@holo-js/forms 0.3.15 → 0.3.17
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/{contracts-BOhBkI9c.d.ts → contracts-CJ9Aq8pS.d.ts} +9 -9
- package/dist/index.d.ts +2 -3
- package/dist/index.mjs +5 -25
- package/dist/internal/client.d.ts +25 -10
- package/dist/internal/client.mjs +122 -105
- package/package.json +4 -9
- package/dist/chunk-GPWXGHZE.mjs +0 -40
- package/dist/schema.d.ts +0 -13
- package/dist/schema.mjs +0 -26
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { ValidationSchema, SchemaInputShape, ValidationErrorBag, InferSchemaData, FormLikeValidationInput } from '@holo-js/validation';
|
|
2
|
-
import { FormSchema } from './schema.js';
|
|
1
|
+
import { ValidationSchema, SchemaInputShape, ValidationErrorBag, InferValidationSchemaData, InferSchemaData, FormLikeValidationInput } from '@holo-js/validation';
|
|
3
2
|
|
|
4
3
|
type FormFailureErrors = Readonly<Partial<Record<string, readonly string[]>>>;
|
|
5
4
|
interface FormFailureOptions {
|
|
@@ -17,6 +16,7 @@ declare const sensitiveInputInternals: {
|
|
|
17
16
|
DEFAULT_DONT_FLASH_FIELDS: readonly string[];
|
|
18
17
|
};
|
|
19
18
|
|
|
19
|
+
type InferFormData<TSchema extends ValidationSchema> = InferValidationSchemaData<TSchema>;
|
|
20
20
|
interface FormFailurePayload<TData> {
|
|
21
21
|
readonly ok: false;
|
|
22
22
|
readonly status: number;
|
|
@@ -109,15 +109,15 @@ interface FormSubmissionFailure<TData> {
|
|
|
109
109
|
fail(options: FormFailureOptions): FormFailurePayload<TData>;
|
|
110
110
|
}
|
|
111
111
|
type FormSubmissionResult<TData> = FormSubmissionSuccess<TData> | FormSubmissionFailure<TData>;
|
|
112
|
-
declare function serializeSubmissionState<TData>(valid: boolean, values: Partial<TData> | TData, errors: ValidationErrorBag<TData>, schemaDefinition?:
|
|
113
|
-
declare function createSubmission<TData>(valid: boolean, values: Partial<TData> | TData, errors: ValidationErrorBag<TData>, failureStatus?: number, schemaDefinition?:
|
|
114
|
-
declare function createSuccessfulSubmission<TShape extends SchemaInputShape>(schemaDefinition:
|
|
115
|
-
declare function createFailedSubmission<TShape extends SchemaInputShape>(schemaDefinition:
|
|
112
|
+
declare function serializeSubmissionState<TData>(valid: boolean, values: Partial<TData> | TData, errors: ValidationErrorBag<TData>, schemaDefinition?: ValidationSchema): SerializedFormSubmission<TData>;
|
|
113
|
+
declare function createSubmission<TData>(valid: boolean, values: Partial<TData> | TData, errors: ValidationErrorBag<TData>, failureStatus?: number, schemaDefinition?: ValidationSchema, failureMetadata?: FormFailureMetadata): FormSubmissionResult<TData>;
|
|
114
|
+
declare function createSuccessfulSubmission<TShape extends SchemaInputShape>(schemaDefinition: ValidationSchema<TShape>, data: InferSchemaData<TShape>): FormSubmissionSuccess<InferSchemaData<TShape>>;
|
|
115
|
+
declare function createFailedSubmission<TShape extends SchemaInputShape>(schemaDefinition: ValidationSchema<TShape>, values: Partial<InferSchemaData<TShape>>, flattenedErrors: Record<string, readonly string[]>, status?: number, failureMetadata?: FormFailureMetadata): FormSubmissionFailure<InferSchemaData<TShape>>;
|
|
116
116
|
declare function isRequestLikeHeaders(value: unknown): value is RequestLikeHeaders;
|
|
117
117
|
declare function normalizeRequestHeaders(input: unknown): Headers;
|
|
118
118
|
declare function normalizeRequestLikeInput(input: FormLikeValidationInput | FormRequestLikeInput | null | undefined): Request | undefined;
|
|
119
|
-
declare function safeParse<TShape extends SchemaInputShape>(input: FormLikeValidationInput | FormRequestLikeInput, schemaDefinition:
|
|
120
|
-
declare function validate<TShape extends SchemaInputShape>(input: FormLikeValidationInput | FormRequestLikeInput, schemaDefinition:
|
|
119
|
+
declare function safeParse<TShape extends SchemaInputShape>(input: FormLikeValidationInput | FormRequestLikeInput, schemaDefinition: ValidationSchema<TShape>, options?: FormSecurityOptions): Promise<FormSubmissionResult<InferSchemaData<TShape>>>;
|
|
120
|
+
declare function validate<TShape extends SchemaInputShape>(input: FormLikeValidationInput | FormRequestLikeInput, schemaDefinition: ValidationSchema<TShape>, options?: FormSecurityOptions): Promise<InferSchemaData<TShape>>;
|
|
121
121
|
declare const formsInternals: {
|
|
122
122
|
createSubmission: typeof createSubmission;
|
|
123
123
|
isRequestLikeHeaders: typeof isRequestLikeHeaders;
|
|
@@ -129,4 +129,4 @@ declare const formsInternals: {
|
|
|
129
129
|
serializeSubmissionState: typeof serializeSubmissionState;
|
|
130
130
|
};
|
|
131
131
|
|
|
132
|
-
export { type FormFailureErrors as F, type SerializedFormSubmission as S, type FormFailureInput as a, type FormFailureOptions as b, type FormFailurePayload as c, type FormRequestLikeInput as d, type FormSecurityOptions as e, type FormSubmissionFailure as f, type FormSubmissionResult as g, type FormSubmissionSuccess as h, type FormSuccessPayload as i, createFailedSubmission as j, createSuccessfulSubmission as k, formsInternals as l, sanitizeFlashedInput as m, sensitiveInputInternals as n, safeParse as s, validate as v };
|
|
132
|
+
export { type FormFailureErrors as F, type InferFormData as I, type SerializedFormSubmission as S, type FormFailureInput as a, type FormFailureOptions as b, type FormFailurePayload as c, type FormRequestLikeInput as d, type FormSecurityOptions as e, type FormSubmissionFailure as f, type FormSubmissionResult as g, type FormSubmissionSuccess as h, type FormSuccessPayload as i, createFailedSubmission as j, createSuccessfulSubmission as k, formsInternals as l, sanitizeFlashedInput as m, sensitiveInputInternals as n, safeParse as s, validate as v };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
export { F as FormFailureErrors, a as FormFailureInput, b as FormFailureOptions, c as FormFailurePayload, d as FormRequestLikeInput, e as FormSecurityOptions, f as FormSubmissionFailure, g as FormSubmissionResult, h as FormSubmissionSuccess, i as FormSuccessPayload, S as SerializedFormSubmission, j as createFailedSubmission, k as createSuccessfulSubmission, l as formsInternals, s as safeParse, m as sanitizeFlashedInput, n as sensitiveInputInternals, v as validate } from './contracts-
|
|
2
|
-
|
|
3
|
-
export { DEFAULT_VALIDATION_BAG, ErrorTree, ErrorTreeNode, FieldDefinition, FieldRule, InferSchemaData, SerializedValidationException, StandardSchemaV1, StandardSchemaV1Issue, StandardSchemaV1Props, StandardSchemaV1Result, ValidationErrorBag, ValidationException, ValidationExceptionOptions, ValidationFailure, ValidationResult, ValidationSchema, ValidationSuccess, WebFileLike, createErrorBag, defineSchema, field, isValidationException, parse } from '@holo-js/validation';
|
|
1
|
+
export { F as FormFailureErrors, a as FormFailureInput, b as FormFailureOptions, c as FormFailurePayload, d as FormRequestLikeInput, e as FormSecurityOptions, f as FormSubmissionFailure, g as FormSubmissionResult, h as FormSubmissionSuccess, i as FormSuccessPayload, I as InferFormData, S as SerializedFormSubmission, j as createFailedSubmission, k as createSuccessfulSubmission, l as formsInternals, s as safeParse, m as sanitizeFlashedInput, n as sensitiveInputInternals, v as validate } from './contracts-CJ9Aq8pS.js';
|
|
2
|
+
import '@holo-js/validation';
|
|
4
3
|
|
|
5
4
|
declare class FormContractError extends Error {
|
|
6
5
|
constructor(message: string);
|
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DEFAULT_VALIDATION_BAG,
|
|
3
|
-
ValidationException,
|
|
4
|
-
createErrorBag,
|
|
5
|
-
defineSchema,
|
|
6
|
-
field,
|
|
7
|
-
isFormSchema,
|
|
8
|
-
isValidationException,
|
|
9
|
-
parse,
|
|
10
|
-
schema
|
|
11
|
-
} from "./chunk-GPWXGHZE.mjs";
|
|
12
1
|
import {
|
|
13
2
|
normalizeFailureErrors,
|
|
14
3
|
normalizeFailureInput,
|
|
@@ -22,8 +11,8 @@ import {
|
|
|
22
11
|
|
|
23
12
|
// src/contracts.ts
|
|
24
13
|
import {
|
|
25
|
-
ValidationException
|
|
26
|
-
createErrorBag
|
|
14
|
+
ValidationException,
|
|
15
|
+
createErrorBag,
|
|
27
16
|
safeParse as safeParseInput,
|
|
28
17
|
validationInternals
|
|
29
18
|
} from "@holo-js/validation";
|
|
@@ -89,7 +78,7 @@ function createSuccessfulSubmission(schemaDefinition, data) {
|
|
|
89
78
|
return createSubmission(
|
|
90
79
|
true,
|
|
91
80
|
data,
|
|
92
|
-
|
|
81
|
+
createErrorBag(),
|
|
93
82
|
422,
|
|
94
83
|
schemaDefinition
|
|
95
84
|
);
|
|
@@ -100,7 +89,7 @@ function createFailedSubmission(schemaDefinition, values, flattenedErrors, statu
|
|
|
100
89
|
return createSubmission(
|
|
101
90
|
false,
|
|
102
91
|
values,
|
|
103
|
-
|
|
92
|
+
createErrorBag(flattenedErrors),
|
|
104
93
|
normalizedStatus,
|
|
105
94
|
schemaDefinition,
|
|
106
95
|
failureMetadata
|
|
@@ -479,7 +468,7 @@ async function validate(input, schemaDefinition, options = {}) {
|
|
|
479
468
|
}
|
|
480
469
|
const failure = result.fail();
|
|
481
470
|
const exception = validationInternals.setValidationExceptionValues(
|
|
482
|
-
|
|
471
|
+
ValidationException.withMessages(result.errors.flatten(), {
|
|
483
472
|
bag: options.bag
|
|
484
473
|
}),
|
|
485
474
|
result.values
|
|
@@ -502,21 +491,12 @@ var formsInternals = {
|
|
|
502
491
|
serializeSubmissionState
|
|
503
492
|
};
|
|
504
493
|
export {
|
|
505
|
-
DEFAULT_VALIDATION_BAG,
|
|
506
494
|
FormContractError,
|
|
507
|
-
ValidationException,
|
|
508
|
-
createErrorBag,
|
|
509
495
|
createFailedSubmission,
|
|
510
496
|
createSuccessfulSubmission,
|
|
511
|
-
defineSchema,
|
|
512
|
-
field,
|
|
513
497
|
formsInternals,
|
|
514
|
-
isFormSchema,
|
|
515
|
-
isValidationException,
|
|
516
|
-
parse,
|
|
517
498
|
safeParse,
|
|
518
499
|
sanitizeFlashedInput,
|
|
519
|
-
schema,
|
|
520
500
|
sensitiveInputInternals,
|
|
521
501
|
validate
|
|
522
502
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { S as SerializedFormSubmission, c as FormFailurePayload, i as FormSuccessPayload, g as FormSubmissionResult } from '../contracts-
|
|
2
|
-
import { ValidationErrorBag, WebFileLike, SchemaInputShape, FieldBuilderInput, InferFieldOutput } from '@holo-js/validation';
|
|
3
|
-
import { FormSchema, InferFormData } from '../schema.js';
|
|
1
|
+
import { S as SerializedFormSubmission, c as FormFailurePayload, i as FormSuccessPayload, g as FormSubmissionResult, I as InferFormData } from '../contracts-CJ9Aq8pS.js';
|
|
2
|
+
import { ValidationErrorBag, WebFileLike, ValidationSchema, SchemaInputShape, FieldBuilderInput, InferFieldOutput } from '@holo-js/validation';
|
|
4
3
|
|
|
5
4
|
declare function collectFormDirtyPaths(current: unknown, initial: unknown, prefix?: string): readonly string[];
|
|
6
5
|
declare class FormClientState<TValues, TSuccess = unknown> {
|
|
@@ -8,21 +7,35 @@ declare class FormClientState<TValues, TSuccess = unknown> {
|
|
|
8
7
|
values: TValues;
|
|
9
8
|
initialValues: TValues;
|
|
10
9
|
flattenedErrors: Record<string, readonly string[]>;
|
|
11
|
-
touched: Set<string>;
|
|
12
|
-
dirty: Set<string>;
|
|
10
|
+
readonly touched: Set<string>;
|
|
13
11
|
lastSubmission?: SerializedFormSubmission<TValues> | FormFailurePayload<TValues> | FormSuccessPayload<TSuccess>;
|
|
14
12
|
readonly listeners: Set<() => void>;
|
|
15
|
-
validationSequence: number;
|
|
16
13
|
constructor(values: TValues, initialState?: SerializedFormSubmission<TValues> | FormFailurePayload<TValues>);
|
|
17
14
|
get submitting(): boolean;
|
|
18
15
|
get errors(): ValidationErrorBag<TValues>;
|
|
19
16
|
get dirtyPaths(): readonly string[];
|
|
20
|
-
|
|
17
|
+
notify(): void;
|
|
18
|
+
invalidateEffects(): void;
|
|
19
|
+
beginValidation(): number;
|
|
20
|
+
isLatestValidation(sequence: number): boolean;
|
|
21
|
+
beginSubmissionEffects(): number;
|
|
22
|
+
ownsSubmissionEffects(sequence: number): boolean;
|
|
23
|
+
touch(path: string): void;
|
|
24
|
+
edit(path: string, value: unknown): void;
|
|
25
|
+
isDirty(path: string): boolean;
|
|
26
|
+
applyValidation(sequence: number, errors: Record<string, readonly string[]>, values?: Partial<TValues>): void;
|
|
27
|
+
applyFieldValidation(sequence: number, path: string, errors: Record<string, readonly string[]>): void;
|
|
28
|
+
applyServer(values: TValues, errors: Record<string, readonly string[]>, submission: FormClientState<TValues, TSuccess>['lastSubmission']): void;
|
|
29
|
+
reset(values?: Partial<TValues>): void;
|
|
21
30
|
startSubmission(signal?: AbortSignal): () => void;
|
|
22
31
|
}
|
|
23
32
|
|
|
24
33
|
type PrimitiveLike = string | number | boolean | bigint | symbol | null | undefined | Date | Blob | WebFileLike;
|
|
25
34
|
type ValidateOnMode = 'submit' | 'blur' | 'change';
|
|
35
|
+
type FormRequestCredentials = 'omit' | 'same-origin' | 'include';
|
|
36
|
+
interface ClientCsrfOptions {
|
|
37
|
+
readonly endpoint: string;
|
|
38
|
+
}
|
|
26
39
|
interface ClientSubmitContext<TData> {
|
|
27
40
|
readonly action?: string;
|
|
28
41
|
readonly method: string;
|
|
@@ -41,6 +54,8 @@ type BrowserFormElement = {
|
|
|
41
54
|
interface UseFormOptions<TData, TSuccess = unknown> {
|
|
42
55
|
readonly action?: string;
|
|
43
56
|
readonly method?: string;
|
|
57
|
+
readonly credentials?: FormRequestCredentials;
|
|
58
|
+
readonly csrf?: ClientCsrfOptions;
|
|
44
59
|
readonly validateOn?: ValidateOnMode;
|
|
45
60
|
readonly initialValues?: Partial<TData>;
|
|
46
61
|
readonly initialState?: SerializedFormSubmission<TData> | FormFailurePayload<TData> | null;
|
|
@@ -63,7 +78,7 @@ type FormFieldTree<TData> = [TData] extends [readonly unknown[]] ? FormFieldStat
|
|
|
63
78
|
type FormFieldTreeFromShape<TShape extends SchemaInputShape> = {
|
|
64
79
|
readonly [K in Extract<keyof TShape, string>]: TShape[K] extends FieldBuilderInput ? FormFieldState<InferFieldOutput<TShape[K]>> : TShape[K] extends SchemaInputShape ? FormFieldTreeFromShape<TShape[K]> : never;
|
|
65
80
|
};
|
|
66
|
-
type InferFormFieldTree<TSchema extends
|
|
81
|
+
type InferFormFieldTree<TSchema extends ValidationSchema> = TSchema extends ValidationSchema<infer TShape> ? FormFieldTreeFromShape<TShape> : never;
|
|
67
82
|
interface UseFormResult<TData, TSuccess = unknown, TFields = FormFieldTree<TData>> {
|
|
68
83
|
readonly fields: TFields;
|
|
69
84
|
readonly values: TData;
|
|
@@ -83,6 +98,6 @@ declare function runWithBrowserFormElement<TData, TSuccess>(formClient: Pick<Use
|
|
|
83
98
|
/**
|
|
84
99
|
* @internal Shared headless form runtime for framework adapters.
|
|
85
100
|
*/
|
|
86
|
-
declare function createFormClient<TSchema extends
|
|
101
|
+
declare function createFormClient<TSchema extends ValidationSchema, TSuccess = unknown>(schemaDefinition: TSchema, options?: UseFormOptions<InferFormData<TSchema>, TSuccess>): UseFormResult<InferFormData<TSchema>, TSuccess, InferFormFieldTree<TSchema>>;
|
|
87
102
|
|
|
88
|
-
export { type ClientSubmitContext, type ClientSubmitResult, FormClientState, type FormFieldState, type FormFieldTree, type InferFormFieldTree, type UseFormOptions, type UseFormResult, type ValidateOnMode, collectFormDirtyPaths, createFormClient, markClientSubmitControlFlowError, runWithBrowserFormElement };
|
|
103
|
+
export { type ClientCsrfOptions, type ClientSubmitContext, type ClientSubmitResult, FormClientState, type FormFieldState, type FormFieldTree, type FormRequestCredentials, type InferFormFieldTree, type UseFormOptions, type UseFormResult, type ValidateOnMode, collectFormDirtyPaths, createFormClient, markClientSubmitControlFlowError, runWithBrowserFormElement };
|
package/dist/internal/client.mjs
CHANGED
|
@@ -275,10 +275,10 @@ var FormClientState = class {
|
|
|
275
275
|
initialValues;
|
|
276
276
|
flattenedErrors;
|
|
277
277
|
touched = /* @__PURE__ */ new Set();
|
|
278
|
-
dirty = /* @__PURE__ */ new Set();
|
|
279
278
|
lastSubmission;
|
|
280
279
|
listeners = /* @__PURE__ */ new Set();
|
|
281
|
-
validationSequence = 0;
|
|
280
|
+
#validationSequence = 0;
|
|
281
|
+
#submissionSequence = 0;
|
|
282
282
|
#submissions = /* @__PURE__ */ new Set();
|
|
283
283
|
constructor(values, initialState) {
|
|
284
284
|
this.values = cloneFormValue(values);
|
|
@@ -295,12 +295,68 @@ var FormClientState = class {
|
|
|
295
295
|
get dirtyPaths() {
|
|
296
296
|
return collectFormDirtyPaths(this.values, this.initialValues);
|
|
297
297
|
}
|
|
298
|
-
|
|
298
|
+
notify() {
|
|
299
|
+
for (const listener of this.listeners) listener();
|
|
300
|
+
}
|
|
301
|
+
invalidateEffects() {
|
|
302
|
+
this.#validationSequence += 1;
|
|
303
|
+
this.#submissionSequence += 1;
|
|
304
|
+
}
|
|
305
|
+
beginValidation() {
|
|
306
|
+
return ++this.#validationSequence;
|
|
307
|
+
}
|
|
308
|
+
isLatestValidation(sequence) {
|
|
309
|
+
return sequence === this.#validationSequence;
|
|
310
|
+
}
|
|
311
|
+
beginSubmissionEffects() {
|
|
312
|
+
this.#validationSequence += 1;
|
|
313
|
+
return ++this.#submissionSequence;
|
|
314
|
+
}
|
|
315
|
+
ownsSubmissionEffects(sequence) {
|
|
316
|
+
return sequence === this.#submissionSequence;
|
|
317
|
+
}
|
|
318
|
+
touch(path) {
|
|
319
|
+
this.touched.add(path);
|
|
320
|
+
}
|
|
321
|
+
edit(path, value) {
|
|
322
|
+
this.invalidateEffects();
|
|
323
|
+
setFormValueAtPath(this.values, path, value);
|
|
324
|
+
this.touch(path);
|
|
325
|
+
}
|
|
326
|
+
isDirty(path) {
|
|
327
|
+
return !areFormValuesEqual(getFormValueAtPath(this.values, path), getFormValueAtPath(this.initialValues, path));
|
|
328
|
+
}
|
|
329
|
+
applyValidation(sequence, errors, values) {
|
|
330
|
+
if (!this.isLatestValidation(sequence)) return;
|
|
331
|
+
if (values) this.values = mergeFormValues(this.values, values);
|
|
332
|
+
this.flattenedErrors = errors;
|
|
333
|
+
this.notify();
|
|
334
|
+
}
|
|
335
|
+
applyFieldValidation(sequence, path, errors) {
|
|
336
|
+
if (!this.isLatestValidation(sequence)) return;
|
|
337
|
+
const prefix = `${path}.`;
|
|
338
|
+
const merged = Object.fromEntries(Object.entries(this.flattenedErrors).filter(([key]) => key !== path && !key.startsWith(prefix)));
|
|
339
|
+
for (const [key, messages] of Object.entries(errors)) {
|
|
340
|
+
if (key === path || key.startsWith(prefix)) merged[key] = messages;
|
|
341
|
+
}
|
|
342
|
+
this.applyValidation(sequence, merged);
|
|
343
|
+
}
|
|
344
|
+
applyServer(values, errors, submission) {
|
|
345
|
+
this.#validationSequence += 1;
|
|
299
346
|
this.values = values;
|
|
300
|
-
this.initialValues = initialValues;
|
|
301
347
|
this.flattenedErrors = errors;
|
|
302
|
-
this.
|
|
303
|
-
this.
|
|
348
|
+
this.lastSubmission = submission;
|
|
349
|
+
this.notify();
|
|
350
|
+
}
|
|
351
|
+
reset(values) {
|
|
352
|
+
const next = mergeFormValues(this.initialValues, values);
|
|
353
|
+
this.invalidateEffects();
|
|
354
|
+
this.values = cloneFormValue(next);
|
|
355
|
+
this.initialValues = cloneFormValue(next);
|
|
356
|
+
this.flattenedErrors = {};
|
|
357
|
+
this.touched.clear();
|
|
358
|
+
this.lastSubmission = void 0;
|
|
359
|
+
this.notify();
|
|
304
360
|
}
|
|
305
361
|
startSubmission(signal) {
|
|
306
362
|
const id = /* @__PURE__ */ Symbol();
|
|
@@ -446,36 +502,12 @@ async function runWithBrowserFormElement(formClient, form) {
|
|
|
446
502
|
}
|
|
447
503
|
return await formClient.submit();
|
|
448
504
|
}
|
|
449
|
-
function notifyListeners(state) {
|
|
450
|
-
for (const listener of state.listeners) {
|
|
451
|
-
listener();
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
function nextValidationSequence(state) {
|
|
455
|
-
state.validationSequence += 1;
|
|
456
|
-
return state.validationSequence;
|
|
457
|
-
}
|
|
458
|
-
function isLatestValidation(state, sequence) {
|
|
459
|
-
return sequence === state.validationSequence;
|
|
460
|
-
}
|
|
461
505
|
function collectErrorsForPath(flattenedErrors, path) {
|
|
462
506
|
const direct = flattenedErrors[path] ?? [];
|
|
463
507
|
const prefix = `${path}.`;
|
|
464
508
|
const nested = Object.entries(flattenedErrors).filter(([key]) => key.startsWith(prefix)).flatMap(([, messages]) => messages);
|
|
465
509
|
return direct.length > 0 || nested.length > 0 ? [...direct, ...nested] : [];
|
|
466
510
|
}
|
|
467
|
-
function replaceErrorsForPath(flattenedErrors, path, nextErrors) {
|
|
468
|
-
const prefix = `${path}.`;
|
|
469
|
-
const merged = Object.fromEntries(
|
|
470
|
-
Object.entries(flattenedErrors).filter(([key]) => key !== path && !key.startsWith(prefix))
|
|
471
|
-
);
|
|
472
|
-
for (const [key, messages] of Object.entries(nextErrors)) {
|
|
473
|
-
if (key === path || key.startsWith(prefix)) {
|
|
474
|
-
merged[key] = messages;
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
return merged;
|
|
478
|
-
}
|
|
479
511
|
function buildFieldsTree(state, schemaDefinition, source, validateOn, prefix = "") {
|
|
480
512
|
if (isFormLeafValue(source)) {
|
|
481
513
|
const path = prefix;
|
|
@@ -493,56 +525,41 @@ function buildFieldsTree(state, schemaDefinition, source, validateOn, prefix = "
|
|
|
493
525
|
return state.touched.has(path);
|
|
494
526
|
},
|
|
495
527
|
get dirty() {
|
|
496
|
-
return state.
|
|
528
|
+
return state.isDirty(path);
|
|
497
529
|
},
|
|
498
530
|
async set(value) {
|
|
499
|
-
|
|
500
|
-
state.touched.add(path);
|
|
501
|
-
if (!areFormValuesEqual(getFormValueAtPath(state.initialValues, path), value)) {
|
|
502
|
-
state.dirty.add(path);
|
|
503
|
-
} else {
|
|
504
|
-
state.dirty.delete(path);
|
|
505
|
-
}
|
|
531
|
+
state.edit(path, value);
|
|
506
532
|
if (validateOn === "change") {
|
|
507
|
-
const sequence =
|
|
533
|
+
const sequence = state.beginValidation();
|
|
508
534
|
const submission = await validateClientValues(
|
|
509
535
|
cloneFormValue(state.values),
|
|
510
536
|
schemaDefinition
|
|
511
537
|
);
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
}
|
|
538
|
+
state.applyValidation(sequence, submission.errors.flatten());
|
|
539
|
+
return;
|
|
515
540
|
}
|
|
516
|
-
|
|
541
|
+
state.notify();
|
|
517
542
|
},
|
|
518
543
|
async onInput(value) {
|
|
519
544
|
await this.set(value);
|
|
520
545
|
},
|
|
521
546
|
async onBlur() {
|
|
522
|
-
state.
|
|
547
|
+
state.touch(path);
|
|
523
548
|
if (validateOn === "blur") {
|
|
524
|
-
const sequence =
|
|
549
|
+
const sequence = state.beginValidation();
|
|
525
550
|
const submission = await validateClientValues(
|
|
526
551
|
cloneFormValue(state.values),
|
|
527
552
|
schemaDefinition
|
|
528
553
|
);
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
state.flattenedErrors,
|
|
532
|
-
path,
|
|
533
|
-
submission.errors.flatten()
|
|
534
|
-
);
|
|
535
|
-
}
|
|
554
|
+
state.applyFieldValidation(sequence, path, submission.errors.flatten());
|
|
555
|
+
return;
|
|
536
556
|
}
|
|
537
|
-
|
|
557
|
+
state.notify();
|
|
538
558
|
},
|
|
539
559
|
async validate() {
|
|
540
|
-
const sequence =
|
|
560
|
+
const sequence = state.beginValidation();
|
|
541
561
|
const submission = await validateClientValues(cloneFormValue(state.values), schemaDefinition);
|
|
542
|
-
|
|
543
|
-
state.flattenedErrors = submission.errors.flatten();
|
|
544
|
-
notifyListeners(state);
|
|
545
|
-
}
|
|
562
|
+
state.applyValidation(sequence, submission.errors.flatten());
|
|
546
563
|
return state.flattenedErrors[path] ?? [];
|
|
547
564
|
}
|
|
548
565
|
});
|
|
@@ -601,7 +618,7 @@ function normalizeSubmissionLike(schemaDefinition, values, result) {
|
|
|
601
618
|
valid: false,
|
|
602
619
|
values: result.values,
|
|
603
620
|
errors: result.errors,
|
|
604
|
-
...result.status === 429 && "
|
|
621
|
+
...result.status === 429 && "retryAfterSeconds" in result ? { retryAfterSeconds: result.retryAfterSeconds } : {},
|
|
605
622
|
...result.status === 429 && "retryAt" in result ? { retryAt: result.retryAt } : {}
|
|
606
623
|
};
|
|
607
624
|
}
|
|
@@ -659,7 +676,7 @@ async function normalizeFetchResponse(response, fallbackValues) {
|
|
|
659
676
|
return createTransportFailure(fallbackValues, response.status);
|
|
660
677
|
}
|
|
661
678
|
}
|
|
662
|
-
async function defaultSubmitter(context) {
|
|
679
|
+
async function defaultSubmitter(context, options) {
|
|
663
680
|
if (typeof fetch !== "function" || !context.action) {
|
|
664
681
|
return {
|
|
665
682
|
ok: true,
|
|
@@ -668,9 +685,11 @@ async function defaultSubmitter(context) {
|
|
|
668
685
|
};
|
|
669
686
|
}
|
|
670
687
|
const method = context.method.toUpperCase();
|
|
688
|
+
const credentials = options.credentials ? { credentials: options.credentials } : {};
|
|
671
689
|
if (method === "GET" || method === "HEAD") {
|
|
672
690
|
const response2 = await fetch(appendQueryString(context.action, context.formData), {
|
|
673
|
-
method
|
|
691
|
+
method,
|
|
692
|
+
...credentials
|
|
674
693
|
});
|
|
675
694
|
if (method === "HEAD" || response2.status === 204 || response2.status === 205) {
|
|
676
695
|
return {
|
|
@@ -681,8 +700,24 @@ async function defaultSubmitter(context) {
|
|
|
681
700
|
}
|
|
682
701
|
return await normalizeFetchResponse(response2, context.values);
|
|
683
702
|
}
|
|
703
|
+
const headers = new Headers();
|
|
704
|
+
if (options.csrf) {
|
|
705
|
+
const csrfResponse = await fetch(options.csrf.endpoint, {
|
|
706
|
+
...credentials
|
|
707
|
+
});
|
|
708
|
+
if (!csrfResponse.ok) {
|
|
709
|
+
return createTransportFailure(context.values, csrfResponse.status);
|
|
710
|
+
}
|
|
711
|
+
const csrfPayload = await csrfResponse.json();
|
|
712
|
+
if (!csrfPayload || typeof csrfPayload !== "object" || !("token" in csrfPayload) || typeof csrfPayload.token !== "string" || !csrfPayload.token) {
|
|
713
|
+
return createTransportFailure(context.values);
|
|
714
|
+
}
|
|
715
|
+
headers.set("X-CSRF-TOKEN", csrfPayload.token);
|
|
716
|
+
}
|
|
684
717
|
const response = await fetch(context.action, {
|
|
685
718
|
method,
|
|
719
|
+
...credentials,
|
|
720
|
+
...options.csrf ? { headers } : {},
|
|
686
721
|
body: context.formData
|
|
687
722
|
});
|
|
688
723
|
return await normalizeFetchResponse(response, context.values);
|
|
@@ -710,28 +745,21 @@ function createFormClient(schemaDefinition, options = {}) {
|
|
|
710
745
|
return await runValidationForInput(cloneFormValue(state.values));
|
|
711
746
|
}
|
|
712
747
|
async function runValidationForInput(input) {
|
|
713
|
-
const sequence =
|
|
748
|
+
const sequence = state.beginValidation();
|
|
714
749
|
const submission = await validateClientValues(input, schemaDefinition);
|
|
715
|
-
|
|
716
|
-
state.values = mergeFormValues(
|
|
717
|
-
state.values,
|
|
718
|
-
submission.valid ? submission.data : submission.values
|
|
719
|
-
);
|
|
720
|
-
state.flattenedErrors = submission.errors.flatten();
|
|
721
|
-
notifyListeners(state);
|
|
722
|
-
}
|
|
750
|
+
state.applyValidation(sequence, submission.errors.flatten(), submission.valid ? submission.data : submission.values);
|
|
723
751
|
return submission;
|
|
724
752
|
}
|
|
725
|
-
function applyServerState(result) {
|
|
753
|
+
function applyServerState(result, submissionSequence) {
|
|
726
754
|
const normalized = normalizeSubmissionLike(
|
|
727
755
|
schemaDefinition,
|
|
728
756
|
state.values,
|
|
729
757
|
result
|
|
730
758
|
);
|
|
759
|
+
const applies = submissionSequence === void 0 || state.ownsSubmissionEffects(submissionSequence);
|
|
760
|
+
if (submissionSequence === void 0) state.invalidateEffects();
|
|
731
761
|
if ("ok" in normalized && normalized.ok === true) {
|
|
732
|
-
state.
|
|
733
|
-
state.flattenedErrors = {};
|
|
734
|
-
notifyListeners(state);
|
|
762
|
+
if (applies) state.applyServer(state.values, {}, normalized);
|
|
735
763
|
return normalized;
|
|
736
764
|
}
|
|
737
765
|
if ("ok" in normalized && normalized.ok === false) {
|
|
@@ -739,26 +767,27 @@ function createFormClient(schemaDefinition, options = {}) {
|
|
|
739
767
|
...normalized,
|
|
740
768
|
values: sanitizeFlashedInput(normalized.values, schemaDefinition)
|
|
741
769
|
};
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
770
|
+
if (applies) {
|
|
771
|
+
const values = mergeFormValues(state.values, sanitized.values);
|
|
772
|
+
clearSensitiveInputValues(values, schemaDefinition);
|
|
773
|
+
state.applyServer(values, sanitized.errors, sanitized);
|
|
774
|
+
}
|
|
747
775
|
return sanitized;
|
|
748
776
|
}
|
|
749
777
|
const normalizedSubmission = normalized;
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
778
|
+
if (applies) {
|
|
779
|
+
const values = mergeFormValues(state.values, normalizedSubmission.values);
|
|
780
|
+
clearSensitiveInputValues(values, schemaDefinition);
|
|
781
|
+
state.applyServer(values, normalizedSubmission.errors.flatten(), normalizedSubmission.valid ? void 0 : normalizedSubmission.fail());
|
|
782
|
+
}
|
|
755
783
|
return normalizedSubmission;
|
|
756
784
|
}
|
|
757
785
|
async function submit(browserForm) {
|
|
786
|
+
const submissionSequence = state.beginSubmissionEffects();
|
|
758
787
|
const finishSubmission = state.startSubmission();
|
|
759
|
-
|
|
788
|
+
state.notify();
|
|
760
789
|
try {
|
|
761
|
-
const submitter = options.submitter ?? defaultSubmitter;
|
|
790
|
+
const submitter = options.submitter ?? ((context) => defaultSubmitter(context, options));
|
|
762
791
|
const liveForm = browserForm ?? getActiveElementBrowserForm();
|
|
763
792
|
const method = options.method ?? liveForm?.method ?? "POST";
|
|
764
793
|
const action = options.action ?? liveForm?.action;
|
|
@@ -787,17 +816,18 @@ function createFormClient(schemaDefinition, options = {}) {
|
|
|
787
816
|
throw error;
|
|
788
817
|
}
|
|
789
818
|
return applyServerState(
|
|
790
|
-
validationExceptionToFailure(error,
|
|
819
|
+
validationExceptionToFailure(error, localSubmission.data) ?? createTransportFailure(localSubmission.data),
|
|
820
|
+
submissionSequence
|
|
791
821
|
);
|
|
792
822
|
}
|
|
793
823
|
return applyServerState(response ?? {
|
|
794
824
|
ok: true,
|
|
795
825
|
status: 204,
|
|
796
826
|
data: void 0
|
|
797
|
-
});
|
|
827
|
+
}, submissionSequence);
|
|
798
828
|
} finally {
|
|
799
829
|
finishSubmission();
|
|
800
|
-
|
|
830
|
+
state.notify();
|
|
801
831
|
}
|
|
802
832
|
}
|
|
803
833
|
return Object.freeze({
|
|
@@ -837,28 +867,15 @@ function createFormClient(schemaDefinition, options = {}) {
|
|
|
837
867
|
return submit(form);
|
|
838
868
|
},
|
|
839
869
|
reset(values) {
|
|
840
|
-
|
|
841
|
-
state.values = cloneFormValue(next);
|
|
842
|
-
state.initialValues = cloneFormValue(next);
|
|
843
|
-
state.flattenedErrors = {};
|
|
844
|
-
state.touched.clear();
|
|
845
|
-
state.dirty.clear();
|
|
846
|
-
state.lastSubmission = void 0;
|
|
847
|
-
notifyListeners(state);
|
|
870
|
+
state.reset(values);
|
|
848
871
|
},
|
|
849
872
|
async setValue(path, value) {
|
|
850
|
-
|
|
851
|
-
state.touched.add(path);
|
|
852
|
-
if (!areFormValuesEqual(getFormValueAtPath(state.initialValues, path), value)) {
|
|
853
|
-
state.dirty.add(path);
|
|
854
|
-
} else {
|
|
855
|
-
state.dirty.delete(path);
|
|
856
|
-
}
|
|
873
|
+
state.edit(path, value);
|
|
857
874
|
if (validateOn === "change" && fieldPaths.includes(path)) {
|
|
858
875
|
await runValidation();
|
|
859
876
|
return;
|
|
860
877
|
}
|
|
861
|
-
|
|
878
|
+
state.notify();
|
|
862
879
|
},
|
|
863
880
|
applyServerState(result) {
|
|
864
881
|
return applyServerState(result);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holo-js/forms",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.17",
|
|
4
4
|
"description": "Holo-JS Framework - forms-first validation and submission contracts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -10,11 +10,6 @@
|
|
|
10
10
|
"import": "./dist/index.mjs",
|
|
11
11
|
"default": "./dist/index.mjs"
|
|
12
12
|
},
|
|
13
|
-
"./schema": {
|
|
14
|
-
"types": "./dist/schema.d.ts",
|
|
15
|
-
"import": "./dist/schema.mjs",
|
|
16
|
-
"default": "./dist/schema.mjs"
|
|
17
|
-
},
|
|
18
13
|
"./internal/client": {
|
|
19
14
|
"types": "./dist/internal/client.d.ts",
|
|
20
15
|
"import": "./dist/internal/client.mjs",
|
|
@@ -33,10 +28,10 @@
|
|
|
33
28
|
"test": "vitest --run"
|
|
34
29
|
},
|
|
35
30
|
"dependencies": {
|
|
36
|
-
"@holo-js/validation": "^0.3.
|
|
31
|
+
"@holo-js/validation": "^0.3.17"
|
|
37
32
|
},
|
|
38
33
|
"peerDependencies": {
|
|
39
|
-
"@holo-js/security": "^0.3.
|
|
34
|
+
"@holo-js/security": "^0.3.17"
|
|
40
35
|
},
|
|
41
36
|
"peerDependenciesMeta": {
|
|
42
37
|
"@holo-js/security": {
|
|
@@ -50,6 +45,6 @@
|
|
|
50
45
|
"vitest": "^4.1.5"
|
|
51
46
|
},
|
|
52
47
|
"engines": {
|
|
53
|
-
"node": "
|
|
48
|
+
"node": "^22.12.0 || ^24.0.0 || ^26.0.0"
|
|
54
49
|
}
|
|
55
50
|
}
|
package/dist/chunk-GPWXGHZE.mjs
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
// src/schema.ts
|
|
2
|
-
import {
|
|
3
|
-
defineSchema,
|
|
4
|
-
isValidationSchema
|
|
5
|
-
} from "@holo-js/validation";
|
|
6
|
-
import {
|
|
7
|
-
createErrorBag,
|
|
8
|
-
DEFAULT_VALIDATION_BAG,
|
|
9
|
-
defineSchema as defineSchema2,
|
|
10
|
-
field,
|
|
11
|
-
isValidationException,
|
|
12
|
-
parse,
|
|
13
|
-
safeParse,
|
|
14
|
-
ValidationException,
|
|
15
|
-
validationInternals
|
|
16
|
-
} from "@holo-js/validation";
|
|
17
|
-
function schema(shapeOrSchema) {
|
|
18
|
-
const base = isValidationSchema(shapeOrSchema) ? shapeOrSchema : defineSchema(shapeOrSchema);
|
|
19
|
-
return Object.freeze({
|
|
20
|
-
...base,
|
|
21
|
-
mode: "form"
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
function isFormSchema(value) {
|
|
25
|
-
return isValidationSchema(value) && value.mode === "form";
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export {
|
|
29
|
-
schema,
|
|
30
|
-
isFormSchema,
|
|
31
|
-
createErrorBag,
|
|
32
|
-
DEFAULT_VALIDATION_BAG,
|
|
33
|
-
defineSchema2 as defineSchema,
|
|
34
|
-
field,
|
|
35
|
-
isValidationException,
|
|
36
|
-
parse,
|
|
37
|
-
safeParse,
|
|
38
|
-
ValidationException,
|
|
39
|
-
validationInternals
|
|
40
|
-
};
|
package/dist/schema.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { SchemaInputShape, ValidationSchema, InferSchemaData } from '@holo-js/validation';
|
|
2
|
-
export { DEFAULT_VALIDATION_BAG, ErrorTree, ErrorTreeNode, FieldDefinition, FieldRule, InferSchemaData, SerializedValidationException, StandardSchemaV1, StandardSchemaV1Issue, StandardSchemaV1Props, StandardSchemaV1Result, ValidationErrorBag, ValidationException, ValidationExceptionOptions, ValidationFailure, ValidationResult, ValidationSchema, ValidationSuccess, WebFileLike, createErrorBag, defineSchema, field, isValidationException, parse, safeParse, validationInternals } from '@holo-js/validation';
|
|
3
|
-
|
|
4
|
-
interface FormSchema<TShape extends SchemaInputShape = SchemaInputShape> extends ValidationSchema<TShape> {
|
|
5
|
-
readonly mode: 'form';
|
|
6
|
-
readonly fields: ValidationSchema<TShape>['fields'];
|
|
7
|
-
readonly $values?: Partial<InferSchemaData<TShape>>;
|
|
8
|
-
}
|
|
9
|
-
type InferFormData<TSchema extends FormSchema> = TSchema extends FormSchema<infer TShape> ? InferSchemaData<TShape> : never;
|
|
10
|
-
declare function schema<TShape extends SchemaInputShape>(shapeOrSchema: TShape | ValidationSchema<TShape>): FormSchema<TShape>;
|
|
11
|
-
declare function isFormSchema(value: unknown): value is FormSchema;
|
|
12
|
-
|
|
13
|
-
export { type FormSchema, type InferFormData, isFormSchema, schema };
|
package/dist/schema.mjs
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DEFAULT_VALIDATION_BAG,
|
|
3
|
-
ValidationException,
|
|
4
|
-
createErrorBag,
|
|
5
|
-
defineSchema,
|
|
6
|
-
field,
|
|
7
|
-
isFormSchema,
|
|
8
|
-
isValidationException,
|
|
9
|
-
parse,
|
|
10
|
-
safeParse,
|
|
11
|
-
schema,
|
|
12
|
-
validationInternals
|
|
13
|
-
} from "./chunk-GPWXGHZE.mjs";
|
|
14
|
-
export {
|
|
15
|
-
DEFAULT_VALIDATION_BAG,
|
|
16
|
-
ValidationException,
|
|
17
|
-
createErrorBag,
|
|
18
|
-
defineSchema,
|
|
19
|
-
field,
|
|
20
|
-
isFormSchema,
|
|
21
|
-
isValidationException,
|
|
22
|
-
parse,
|
|
23
|
-
safeParse,
|
|
24
|
-
schema,
|
|
25
|
-
validationInternals
|
|
26
|
-
};
|