@holo-js/forms 0.3.28 → 0.4.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Holo-JS contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ 12e0c6925b4706daffb3ba511f35967af401f83673e6acb4448c06ca07a02a8a
@@ -0,0 +1,54 @@
1
+ import {
2
+ FormContractError
3
+ } from "./chunk-OFPH5LBY.mjs";
4
+
5
+ // src/failure.ts
6
+ function normalizeStatus(value, fallback) {
7
+ if (typeof value === "undefined") {
8
+ return fallback;
9
+ }
10
+ if (!Number.isInteger(value) || value < 100) {
11
+ throw new FormContractError("HTTP status codes must be integers greater than or equal to 100.");
12
+ }
13
+ return value;
14
+ }
15
+ function normalizeFailureInput(input, fallbackStatus) {
16
+ if (typeof input === "number") {
17
+ return {
18
+ status: normalizeStatus(input, fallbackStatus)
19
+ };
20
+ }
21
+ return {
22
+ status: normalizeStatus(input?.status, fallbackStatus),
23
+ errors: input?.errors
24
+ };
25
+ }
26
+ function normalizeFailureErrors(fallback, override) {
27
+ if (!override) {
28
+ return fallback;
29
+ }
30
+ const normalized = { ...fallback };
31
+ for (const [field, messages] of Object.entries(override)) {
32
+ if (typeof messages !== "undefined") {
33
+ normalized[field] = messages;
34
+ }
35
+ }
36
+ return normalized;
37
+ }
38
+
39
+ // src/sensitiveInput.ts
40
+ import { validationInternals } from "@holo-js/validation";
41
+ function sanitizeFlashedInput(values, schemaDefinition) {
42
+ return validationInternals.stripSensitiveInput(values, schemaDefinition?.fields, { files: true });
43
+ }
44
+ function clearSensitiveInputValues(values, schemaDefinition) {
45
+ return validationInternals.clearSensitiveInput(values, schemaDefinition?.fields, { files: true });
46
+ }
47
+
48
+ export {
49
+ normalizeStatus,
50
+ normalizeFailureInput,
51
+ normalizeFailureErrors,
52
+ sanitizeFlashedInput,
53
+ clearSensitiveInputValues
54
+ };
@@ -10,12 +10,7 @@ type FormFailureInput = number | FormFailureOptions | undefined;
10
10
  declare function normalizeStatus(value: number | undefined, fallback: number): number;
11
11
 
12
12
  type SensitiveSchema = Pick<ValidationSchema<SchemaInputShape>, 'fields'>;
13
- declare function collectSensitivePaths(schemaDefinition: SensitiveSchema | undefined): readonly string[];
14
13
  declare function sanitizeFlashedInput<TData>(values: Partial<TData> | TData, schemaDefinition?: SensitiveSchema): Partial<TData> | TData;
15
- declare const sensitiveInputInternals: {
16
- collectSensitivePaths: typeof collectSensitivePaths;
17
- DEFAULT_DONT_FLASH_FIELDS: readonly string[];
18
- };
19
14
 
20
15
  type InferFormData<TSchema extends ValidationSchema> = InferValidationSchemaData<TSchema>;
21
16
  interface FormFailurePayload<TData> {
@@ -126,4 +121,5 @@ declare const formsInternals: {
126
121
  serializeSubmissionState: typeof serializeSubmissionState;
127
122
  };
128
123
 
129
- 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 };
124
+ export { createFailedSubmission as j, createSuccessfulSubmission as k, formsInternals as l, sanitizeFlashedInput as m, safeParse as s, validate as v };
125
+ export type { FormFailureErrors as F, InferFormData as I, SerializedFormSubmission as S, FormFailureInput as a, FormFailureOptions as b, FormFailurePayload as c, FormRequestLikeInput as d, FormSecurityOptions as e, FormSubmissionFailure as f, FormSubmissionResult as g, FormSubmissionSuccess as h, FormSuccessPayload as i };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
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-DzAvmYd0.js';
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, v as validate } from './contracts-CN5pB9Tx.js';
2
2
  import '@holo-js/kernel';
3
3
  import '@holo-js/validation';
4
4
 
package/dist/index.mjs CHANGED
@@ -2,15 +2,14 @@ import {
2
2
  normalizeFailureErrors,
3
3
  normalizeFailureInput,
4
4
  normalizeStatus,
5
- sanitizeFlashedInput,
6
- sensitiveInputInternals
7
- } from "./chunk-WJOAU4V2.mjs";
5
+ sanitizeFlashedInput
6
+ } from "./chunk-TFYWXPME.mjs";
8
7
  import {
9
8
  FormContractError
10
9
  } from "./chunk-OFPH5LBY.mjs";
11
10
 
12
11
  // src/contracts.ts
13
- import { resolveRequestUrl } from "@holo-js/kernel";
12
+ import { HoloRequestBodyTooLargeError, readBoundedRequestBody, requestUrlInternals } from "@holo-js/kernel";
14
13
  import {
15
14
  ValidationException,
16
15
  createErrorBag,
@@ -216,7 +215,7 @@ async function resolveAmbientFormDataRequest(input) {
216
215
  return void 0;
217
216
  }
218
217
  const requestHeaders = await nextHeadersModule.headers();
219
- return new Request(resolveRequestUrl({ headers: requestHeaders }), {
218
+ return new Request(requestUrlInternals.resolveRoutedRequestUrl({ headers: requestHeaders }), {
220
219
  method: "POST",
221
220
  headers: createFormDataRequestHeaders(requestHeaders),
222
221
  body: input
@@ -226,7 +225,7 @@ function getStructuredWebRequest(input) {
226
225
  return input.web?.request instanceof Request ? void 0 : input.web?.request;
227
226
  }
228
227
  function extractRequestLikeUrl(input, headers) {
229
- return resolveRequestUrl({ ...input, headers }).href;
228
+ return requestUrlInternals.resolveRoutedRequestUrl({ ...input, headers }).href;
230
229
  }
231
230
  function extractRequestLikeMethod(input) {
232
231
  const webRequest = getStructuredWebRequest(input);
@@ -357,37 +356,33 @@ function createSecurityFailureMetadata(error) {
357
356
  ...typeof retryAt === "string" ? { retryAt } : {}
358
357
  };
359
358
  }
360
- async function createSecurityFailureSubmission(input, schemaDefinition, error) {
361
- let values = {};
362
- let flattenedErrors = {};
363
- try {
364
- const inspection = await safeParseInput(input.clone(), schemaDefinition);
365
- if (inspection.valid) {
366
- values = inspection.data;
367
- } else {
368
- values = inspection.values;
369
- flattenedErrors = inspection.errors.flatten();
370
- }
371
- } catch {
372
- values = {};
373
- flattenedErrors = {};
374
- }
375
- return createFailedSubmission(
376
- schemaDefinition,
377
- values,
378
- {
379
- ...flattenedErrors,
380
- _root: [
381
- ...flattenedErrors._root ?? [],
382
- error.message
383
- ]
384
- },
385
- error.status,
386
- createSecurityFailureMetadata(error)
387
- );
359
+ function captureBodyTooLarge(error) {
360
+ if (error instanceof HoloRequestBodyTooLargeError) return error;
361
+ throw error;
362
+ }
363
+ function submittedRequestInit(request, body) {
364
+ return {
365
+ method: request.method,
366
+ headers: new Headers(request.headers),
367
+ body,
368
+ duplex: "half",
369
+ signal: request.signal,
370
+ credentials: request.credentials,
371
+ redirect: request.redirect,
372
+ referrer: request.referrer,
373
+ referrerPolicy: request.referrerPolicy,
374
+ integrity: request.integrity,
375
+ keepalive: request.keepalive,
376
+ cache: request.cache === "only-if-cached" ? void 0 : request.cache,
377
+ mode: request.mode === "navigate" ? void 0 : request.mode
378
+ };
379
+ }
380
+ async function bufferSubmittedRequest(request) {
381
+ if (request.method === "GET" || request.method === "HEAD") return request;
382
+ const body = await readBoundedRequestBody(request.clone());
383
+ return new Request(request.url, submittedRequestInit(request, body));
388
384
  }
389
385
  async function safeParse(input, schemaDefinition, options = {}) {
390
- let validatedSubmission;
391
386
  const throttle = typeof options.throttle === "string" ? options.throttle : void 0;
392
387
  const usesSecurityOptions = typeof throttle === "string";
393
388
  const normalizedRequestInput = await normalizeRequestLikeInputForValidation(input) ?? (usesSecurityOptions ? await resolveAmbientFormDataRequest(input) : void 0);
@@ -397,45 +392,49 @@ async function safeParse(input, schemaDefinition, options = {}) {
397
392
  "Security-aware safeParse() options require a Request or request-like event input."
398
393
  );
399
394
  }
395
+ const submitted = usesSecurityOptions ? await bufferSubmittedRequest(normalizedRequestInput).catch(captureBodyTooLarge) : validationInput;
396
+ if (submitted instanceof HoloRequestBodyTooLargeError) {
397
+ return createFailedSubmission(schemaDefinition, {}, {
398
+ _root: [submitted.message]
399
+ }, submitted.status);
400
+ }
401
+ const parsedInput = usesSecurityOptions ? submitted.clone() : submitted;
402
+ const result = await safeParseInput(parsedInput, schemaDefinition).catch(captureBodyTooLarge);
403
+ if (result instanceof HoloRequestBodyTooLargeError) {
404
+ return createFailedSubmission(schemaDefinition, {}, {
405
+ _root: [result.message]
406
+ }, result.status);
407
+ }
400
408
  if (usesSecurityOptions) {
401
- const request = normalizedRequestInput;
409
+ const request = submitted;
402
410
  try {
403
411
  const { loadSecurityModule } = await import("./security-YE5UGG47.mjs");
404
412
  const security = await loadSecurityModule();
405
- const inspection = await safeParseInput(request.clone(), schemaDefinition);
406
- const throttleValues = inspection.valid ? inspection.data : inspection.values;
407
- validatedSubmission = inspection.valid ? createSuccessfulSubmission(schemaDefinition, inspection.data) : createFailedSubmission(schemaDefinition, inspection.values, inspection.errors.flatten());
408
413
  await security.rateLimit(throttle, {
409
414
  request,
410
- values: throttleValues
415
+ values: result.valid ? result.data : void 0
411
416
  });
412
417
  } catch (error) {
413
418
  const { formsSecurityInternals } = await import("./security-YE5UGG47.mjs");
414
419
  if (formsSecurityInternals.isRootSecurityError(error)) {
415
- if (validatedSubmission) {
416
- return createFailedSubmission(
417
- schemaDefinition,
418
- validatedSubmission.valid ? validatedSubmission.data : validatedSubmission.values,
419
- {
420
- ...validatedSubmission.valid ? {} : validatedSubmission.errors.flatten(),
421
- _root: [
422
- ...!validatedSubmission.valid ? validatedSubmission.errors.flatten()._root ?? [] : [],
423
- error.message
424
- ]
425
- },
426
- error.status,
427
- createSecurityFailureMetadata(error)
428
- );
429
- }
430
- return await createSecurityFailureSubmission(request, schemaDefinition, error);
420
+ const fieldErrors = result.valid ? {} : result.errors.flatten();
421
+ return createFailedSubmission(
422
+ schemaDefinition,
423
+ result.valid ? result.data : result.values,
424
+ {
425
+ ...fieldErrors,
426
+ _root: [
427
+ ...fieldErrors._root ?? [],
428
+ error.message
429
+ ]
430
+ },
431
+ error.status,
432
+ createSecurityFailureMetadata(error)
433
+ );
431
434
  }
432
435
  throw error;
433
436
  }
434
437
  }
435
- if (validatedSubmission) {
436
- return validatedSubmission;
437
- }
438
- const result = await safeParseInput(validationInput, schemaDefinition);
439
438
  if (result.valid) {
440
439
  return createSuccessfulSubmission(schemaDefinition, result.data);
441
440
  }
@@ -451,7 +450,8 @@ async function validate(input, schemaDefinition, options = {}) {
451
450
  ValidationException.withMessages(result.errors.flatten(), {
452
451
  bag: options.bag
453
452
  }),
454
- result.values
453
+ result.values,
454
+ schemaDefinition
455
455
  );
456
456
  validationInternals.setValidationExceptionStatus(exception, failure.status);
457
457
  validationInternals.setValidationExceptionMetadata(exception, {
@@ -477,6 +477,5 @@ export {
477
477
  formsInternals,
478
478
  safeParse,
479
479
  sanitizeFlashedInput,
480
- sensitiveInputInternals,
481
480
  validate
482
481
  };
@@ -1,4 +1,5 @@
1
- import { S as SerializedFormSubmission, c as FormFailurePayload, i as FormSuccessPayload, g as FormSubmissionResult, I as InferFormData } from '../contracts-DzAvmYd0.js';
1
+ import { SerializedFormSubmission, FormFailurePayload, FormSuccessPayload, FormSubmissionResult, InferFormData } from '../index.js';
2
+ export { m as sanitizeFlashedInput } from '../contracts-CN5pB9Tx.js';
2
3
  import { ValidationErrorBag, WebFileLike, ValidationSchema, SchemaInputShape, FieldBuilderInput, InferFieldOutput } from '@holo-js/validation';
3
4
  import '@holo-js/kernel';
4
5
 
@@ -102,4 +103,5 @@ declare function runWithBrowserFormElement<TData, TSuccess>(formClient: Pick<Use
102
103
  */
103
104
  declare function createFormClient<TSchema extends ValidationSchema, TSuccess = unknown>(schemaDefinition: TSchema, options?: UseFormOptions<InferFormData<TSchema>, TSuccess>): UseFormResult<InferFormData<TSchema>, TSuccess, InferFormFieldTree<TSchema>>;
104
105
 
105
- 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 };
106
+ export { FormClientState, collectFormDirtyPaths, createFormClient, markClientSubmitControlFlowError, runWithBrowserFormElement };
107
+ export type { ClientCsrfOptions, ClientSubmitContext, ClientSubmitResult, FormFieldState, FormFieldTree, FormRequestCredentials, InferFormFieldTree, UseFormOptions, UseFormResult, ValidateOnMode };
@@ -4,7 +4,7 @@ import {
4
4
  normalizeFailureInput,
5
5
  normalizeStatus,
6
6
  sanitizeFlashedInput
7
- } from "../chunk-WJOAU4V2.mjs";
7
+ } from "../chunk-TFYWXPME.mjs";
8
8
  import {
9
9
  createMissingSecurityPackageError,
10
10
  isMissingOptionalPackageError,
@@ -32,20 +32,29 @@ async function loadSecurityClientModule() {
32
32
  });
33
33
  return await securityClientModulePromise;
34
34
  }
35
- async function getClientCsrfField() {
36
- const runtime = globalThis;
37
- if (!runtime.document || typeof runtime.document.cookie !== "string") {
38
- return void 0;
39
- }
40
- let security;
35
+ async function loadInstalledSecurityClientModule() {
41
36
  try {
42
- security = await loadSecurityClientModule();
37
+ return await loadSecurityClientModule();
43
38
  } catch (error) {
44
39
  if (error instanceof FormContractError) {
45
40
  return void 0;
46
41
  }
47
42
  throw error;
48
43
  }
44
+ }
45
+ async function getClientCsrfConfig() {
46
+ const config = (await loadInstalledSecurityClientModule())?.getSecurityClientConfig().csrf;
47
+ return { field: config?.field ?? "_token", header: config?.header ?? "X-CSRF-TOKEN" };
48
+ }
49
+ async function getClientCsrfField() {
50
+ const runtime = globalThis;
51
+ if (!runtime.document || typeof runtime.document.cookie !== "string") {
52
+ return void 0;
53
+ }
54
+ const security = await loadInstalledSecurityClientModule();
55
+ if (!security) {
56
+ return void 0;
57
+ }
49
58
  const config = security.getSecurityClientConfig().csrf;
50
59
  const value = parseCookieHeader(runtime.document.cookie)[config.cookie];
51
60
  if (!value) {
@@ -58,10 +67,7 @@ async function getClientCsrfField() {
58
67
  }
59
68
 
60
69
  // src/internal/validation-exception.ts
61
- import {
62
- isValidationException
63
- } from "@holo-js/validation";
64
- import { parseValidationExceptionDigest } from "@holo-js/validation/internal/compiled";
70
+ import { serializeValidationException } from "@holo-js/validation";
65
71
  function isPlainObject(value) {
66
72
  return !!value && typeof value === "object" && !Array.isArray(value);
67
73
  }
@@ -91,20 +97,7 @@ function formFailurePayloadFromError(error) {
91
97
  return candidates.find((candidate) => isFormFailurePayload(candidate));
92
98
  }
93
99
  function validationExceptionToFailure(error, fallbackValues = {}) {
94
- const payload = isValidationException(error) ? error.toJSON() : parseValidationExceptionDigest(error);
95
- if (payload) {
96
- return {
97
- ok: false,
98
- status: payload.status,
99
- submitted: true,
100
- valid: false,
101
- values: payload.values && Object.keys(payload.values).length > 0 ? payload.values : fallbackValues,
102
- errors: payload.errors,
103
- ...typeof payload.retryAfterSeconds === "number" ? { retryAfterSeconds: payload.retryAfterSeconds } : {},
104
- ...typeof payload.retryAt === "string" ? { retryAt: payload.retryAt } : {}
105
- };
106
- }
107
- const formFailurePayload = formFailurePayloadFromError(error);
100
+ const formFailurePayload = formFailurePayloadFromError(serializeValidationException(error) ?? error);
108
101
  if (formFailurePayload) {
109
102
  return {
110
103
  ok: false,
@@ -184,8 +177,10 @@ function getFormValueAtPath(root, path) {
184
177
  }
185
178
  return cursor;
186
179
  }
180
+ var UNSAFE_FORM_PATH_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
187
181
  function setFormValueAtPath(root, path, value) {
188
182
  const parts = splitFormPath(path);
183
+ if (parts.some((part) => UNSAFE_FORM_PATH_SEGMENTS.has(part))) return;
189
184
  let cursor = root;
190
185
  for (const [index, part] of parts.entries()) {
191
186
  const last = index === parts.length - 1;
@@ -702,7 +697,16 @@ async function defaultSubmitter(context, options) {
702
697
  if (!csrfPayload || typeof csrfPayload !== "object" || !("token" in csrfPayload) || typeof csrfPayload.token !== "string" || !csrfPayload.token) {
703
698
  return createTransportFailure(context.values);
704
699
  }
705
- headers.set("X-CSRF-TOKEN", csrfPayload.token);
700
+ if ("field" in csrfPayload && typeof csrfPayload.field === "string" && csrfPayload.field) {
701
+ context.formData.set(csrfPayload.field, csrfPayload.token);
702
+ } else {
703
+ const config = await getClientCsrfConfig();
704
+ if (config.header.toLowerCase() === "x-xsrf-token") {
705
+ context.formData.set(config.field, csrfPayload.token);
706
+ } else {
707
+ headers.set(config.header, csrfPayload.token);
708
+ }
709
+ }
706
710
  }
707
711
  const response = await fetch(context.action, {
708
712
  method,
@@ -758,16 +762,14 @@ function createFormClient(schemaDefinition, options = {}) {
758
762
  values: sanitizeFlashedInput(normalized.values, schemaDefinition)
759
763
  };
760
764
  if (applies) {
761
- const values = mergeFormValues(state.values, sanitized.values);
762
- clearSensitiveInputValues(values, schemaDefinition);
765
+ const values = clearSensitiveInputValues(mergeFormValues(state.values, sanitized.values), schemaDefinition);
763
766
  state.applyServer(values, sanitized.errors, sanitized);
764
767
  }
765
768
  return sanitized;
766
769
  }
767
770
  const normalizedSubmission = normalized;
768
771
  if (applies) {
769
- const values = mergeFormValues(state.values, normalizedSubmission.values);
770
- clearSensitiveInputValues(values, schemaDefinition);
772
+ const values = clearSensitiveInputValues(mergeFormValues(state.values, normalizedSubmission.values), schemaDefinition);
771
773
  state.applyServer(values, normalizedSubmission.errors.flatten(), normalizedSubmission.valid ? void 0 : normalizedSubmission.fail());
772
774
  }
773
775
  return normalizedSubmission;
@@ -793,9 +795,9 @@ function createFormClient(schemaDefinition, options = {}) {
793
795
  if (!localSubmission.valid) {
794
796
  return localSubmission;
795
797
  }
796
- if (!isSafeMethod(method)) {
798
+ if (!isSafeMethod(method) && !(options.csrf && !options.submitter)) {
797
799
  const csrfField = await getClientCsrfField();
798
- if (csrfField) {
800
+ if (csrfField && !formData.has(csrfField.name)) {
799
801
  formData.set(csrfField.name, csrfField.value);
800
802
  }
801
803
  }
@@ -883,5 +885,6 @@ export {
883
885
  collectFormDirtyPaths,
884
886
  createFormClient,
885
887
  markClientSubmitControlFlowError,
886
- runWithBrowserFormElement
888
+ runWithBrowserFormElement,
889
+ sanitizeFlashedInput
887
890
  };
package/package.json CHANGED
@@ -1,9 +1,14 @@
1
1
  {
2
2
  "name": "@holo-js/forms",
3
- "version": "0.3.28",
3
+ "version": "0.4.0",
4
4
  "description": "Holo-JS Framework - forms-first validation and submission contracts",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/cobraprojects/holo-js.git",
10
+ "directory": "packages/forms"
11
+ },
7
12
  "exports": {
8
13
  ".": {
9
14
  "types": "./dist/index.d.ts",
@@ -19,7 +24,8 @@
19
24
  "main": "./dist/index.mjs",
20
25
  "types": "./dist/index.d.ts",
21
26
  "files": [
22
- "dist"
27
+ "dist",
28
+ "LICENSE"
23
29
  ],
24
30
  "scripts": {
25
31
  "build": "tsup",
@@ -28,11 +34,11 @@
28
34
  "test": "vitest --run && node --test --test-concurrency=3 ../../tests/forms-native-browser.mjs ../../tests/forms-native-packages.mjs"
29
35
  },
30
36
  "dependencies": {
31
- "@holo-js/kernel": "^0.3.28",
32
- "@holo-js/validation": "^0.3.28"
37
+ "@holo-js/kernel": "^0.4.0",
38
+ "@holo-js/validation": "^0.4.0"
33
39
  },
34
40
  "peerDependencies": {
35
- "@holo-js/security": "^0.3.28"
41
+ "@holo-js/security": "^0.4.0"
36
42
  },
37
43
  "peerDependenciesMeta": {
38
44
  "@holo-js/security": {
@@ -40,12 +46,12 @@
40
46
  }
41
47
  },
42
48
  "devDependencies": {
43
- "@types/node": "^22.10.2",
44
- "tsup": "^8.3.5",
45
- "typescript": "^5.7.2",
46
- "vitest": "^4.1.5"
49
+ "@types/node": "^22.20.5",
50
+ "tsup": "^9.0.0",
51
+ "typescript": "^6.0.3",
52
+ "vitest": "^5.0.5"
47
53
  },
48
54
  "engines": {
49
- "node": "^22.12.0 || ^24.0.0 || ^26.0.0"
55
+ "node": "^22.18.0 || ^24.0.0 || ^26.0.0"
50
56
  }
51
57
  }
@@ -1,162 +0,0 @@
1
- import {
2
- FormContractError
3
- } from "./chunk-OFPH5LBY.mjs";
4
-
5
- // src/failure.ts
6
- function normalizeStatus(value, fallback) {
7
- if (typeof value === "undefined") {
8
- return fallback;
9
- }
10
- if (!Number.isInteger(value) || value < 100) {
11
- throw new FormContractError("HTTP status codes must be integers greater than or equal to 100.");
12
- }
13
- return value;
14
- }
15
- function normalizeFailureInput(input, fallbackStatus) {
16
- if (typeof input === "number") {
17
- return {
18
- status: normalizeStatus(input, fallbackStatus)
19
- };
20
- }
21
- return {
22
- status: normalizeStatus(input?.status, fallbackStatus),
23
- errors: input?.errors
24
- };
25
- }
26
- function normalizeFailureErrors(fallback, override) {
27
- if (!override) {
28
- return fallback;
29
- }
30
- const normalized = { ...fallback };
31
- for (const [field, messages] of Object.entries(override)) {
32
- if (typeof messages !== "undefined") {
33
- normalized[field] = messages;
34
- }
35
- }
36
- return normalized;
37
- }
38
-
39
- // src/sensitiveInput.ts
40
- var DEFAULT_DONT_FLASH_FIELDS = Object.freeze([
41
- "confirm_password",
42
- "confirmPassword",
43
- "current_password",
44
- "currentPassword",
45
- "new_password",
46
- "newPassword",
47
- "password",
48
- "password_confirmation",
49
- "passwordConfirmation"
50
- ]);
51
- var DEFAULT_DONT_FLASH_FIELD_SET = new Set(DEFAULT_DONT_FLASH_FIELDS);
52
- function isPlainObject(value) {
53
- return !!value && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof Blob);
54
- }
55
- function isFieldDefinition(value) {
56
- return isPlainObject(value) && typeof value.kind === "string" && Array.isArray(value.rules);
57
- }
58
- function isSchemaField(value) {
59
- return isPlainObject(value) && value.kind === "field" && isFieldDefinition(value.definition);
60
- }
61
- function collectSensitivePathsFromFields(fields, prefix = "", output = []) {
62
- for (const [key, value] of Object.entries(fields)) {
63
- const path = prefix ? `${prefix}.${key}` : key;
64
- if (DEFAULT_DONT_FLASH_FIELD_SET.has(key)) {
65
- output.push(path);
66
- }
67
- if (isSchemaField(value)) {
68
- if (value.definition.sensitive === true || value.definition.kind === "file") {
69
- output.push(path);
70
- }
71
- continue;
72
- }
73
- if (isPlainObject(value)) {
74
- collectSensitivePathsFromFields(value, path, output);
75
- }
76
- }
77
- return output;
78
- }
79
- function collectSensitivePaths(schemaDefinition) {
80
- if (!schemaDefinition) {
81
- return [];
82
- }
83
- return collectSensitivePathsFromFields(schemaDefinition.fields);
84
- }
85
- function deletePath(root, path) {
86
- const parts = path.split(".").filter(Boolean);
87
- const leaf = parts.at(-1);
88
- if (!leaf) {
89
- return;
90
- }
91
- let cursor = root;
92
- for (const part of parts.slice(0, -1)) {
93
- if (!isPlainObject(cursor)) {
94
- return;
95
- }
96
- cursor = cursor[part];
97
- }
98
- if (isPlainObject(cursor)) {
99
- delete cursor[leaf];
100
- }
101
- }
102
- function clearPath(root, path) {
103
- const parts = path.split(".").filter(Boolean);
104
- const leaf = parts.at(-1);
105
- if (!leaf) {
106
- return;
107
- }
108
- let cursor = root;
109
- for (const part of parts.slice(0, -1)) {
110
- if (!isPlainObject(cursor)) {
111
- return;
112
- }
113
- cursor = cursor[part];
114
- }
115
- if (isPlainObject(cursor) && leaf in cursor) {
116
- cursor[leaf] = "";
117
- }
118
- }
119
- function sanitizePlainObject(values, sensitivePaths) {
120
- if (!isPlainObject(values)) {
121
- return values;
122
- }
123
- const output = structuredClone(values);
124
- for (const field of DEFAULT_DONT_FLASH_FIELD_SET) {
125
- delete output[field];
126
- }
127
- for (const path of sensitivePaths) {
128
- deletePath(output, path);
129
- }
130
- return output;
131
- }
132
- function sanitizeFlashedInput(values, schemaDefinition) {
133
- return sanitizePlainObject(values, collectSensitivePaths(schemaDefinition));
134
- }
135
- function clearSensitiveInputValues(values, schemaDefinition) {
136
- if (!isPlainObject(values)) {
137
- return values;
138
- }
139
- const mutableValues = values;
140
- for (const field of DEFAULT_DONT_FLASH_FIELD_SET) {
141
- if (field in mutableValues) {
142
- mutableValues[field] = "";
143
- }
144
- }
145
- for (const path of collectSensitivePaths(schemaDefinition)) {
146
- clearPath(mutableValues, path);
147
- }
148
- return values;
149
- }
150
- var sensitiveInputInternals = {
151
- collectSensitivePaths,
152
- DEFAULT_DONT_FLASH_FIELDS
153
- };
154
-
155
- export {
156
- normalizeStatus,
157
- normalizeFailureInput,
158
- normalizeFailureErrors,
159
- sanitizeFlashedInput,
160
- clearSensitiveInputValues,
161
- sensitiveInputInternals
162
- };