@coherent.js/forms 1.1.2 → 2.0.0-rc.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/README.md +162 -243
- package/dist/csrf.js +57 -0
- package/dist/csrf.js.map +7 -0
- package/dist/form-builder.js +473 -25
- package/dist/form-builder.js.map +3 -3
- package/dist/form-hydration.js +310 -221
- package/dist/form-hydration.js.map +3 -3
- package/dist/index.js +665 -552
- package/dist/index.js.map +4 -4
- package/dist/validation.js +369 -61
- package/dist/validation.js.map +3 -3
- package/dist/validators.js +381 -293
- package/dist/validators.js.map +4 -4
- package/package.json +6 -3
- package/types/csrf.d.ts +45 -0
- package/types/index.d.ts +185 -41
package/dist/validators.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/patterns.js", "../src/validation.js", "../src/validators.js"],
|
|
4
|
-
"sourcesContent": ["/**\n * Shared validation patterns.\n *\n * Internal to @coherent.js/forms \u2014 not re-exported from index.js.\n */\n\n/**\n * Email shape check: a local part, then a dotted domain.\n *\n * Domain labels use `[^\\s@.]` rather than `[^\\s@]` so that the literal dot\n * separators are the only thing that can match a dot. Allowing `[^\\s@]+` on\n * both sides of `\\.` makes the split ambiguous, and a non-matching subject\n * with many dots (\"a@\" + \"a.\" * n + \" \") then costs O(n\u00B2) backtracking \u2014\n * CodeQL js/polynomial-redos.\n *\n * This is a shape check, not RFC 5322 conformance. Deliverability is only\n * ever established by sending mail.\n */\nexport const EMAIL_PATTERN = /^[^\\s@]+@[^\\s@.]+(?:\\.[^\\s@.]+)+$/;\n\n/**\n * Longest address RFC 5321 permits, used to bound work before matching.\n */\nexport const EMAIL_MAX_LENGTH = 254;\n\n/**\n * Test whether a value has the shape of an email address.\n *\n * @param {unknown} value - Value to check\n * @returns {boolean} True if the value looks like an email address\n */\nexport function isEmailShaped(value) {\n return (\n typeof value === 'string' &&\n value.length <= EMAIL_MAX_LENGTH &&\n EMAIL_PATTERN.test(value)\n );\n}\n", "/**\n * Coherent.js Form Validation\n * \n * Comprehensive validation utilities for forms\n * \n * @module forms/validation\n */\n\nimport { isEmailShaped } from './patterns.js';\n\n/**\n * Built-in validators\n */\nexport const validators = {\n required: (message = 'This field is required') => (value) => {\n if (value === null || value === undefined || value === '') {\n return message;\n }\n return null;\n },\n\n minLength: (min, message = `Minimum length is ${min}`) => (value) => {\n if (value && value.length < min) {\n return message;\n }\n return null;\n },\n\n maxLength: (max, message = `Maximum length is ${max}`) => (value) => {\n if (value && value.length > max) {\n return message;\n }\n return null;\n },\n\n min: (min, message = `Minimum value is ${min}`) => (value) => {\n if (value !== null && value !== undefined && Number(value) < min) {\n return message;\n }\n return null;\n },\n\n max: (max, message = `Maximum value is ${max}`) => (value) => {\n if (value !== null && value !== undefined && Number(value) > max) {\n return message;\n }\n return null;\n },\n\n email: (message = 'Invalid email address') => (value) => {\n if (value && !isEmailShaped(value)) {\n return message;\n }\n return null;\n },\n\n url: (message = 'Invalid URL') => (value) => {\n if (value) {\n try {\n new URL(value);\n } catch {\n return message;\n }\n }\n return null;\n },\n\n pattern: (regex, message = 'Invalid format') => (value) => {\n if (value && !regex.test(value)) {\n return message;\n }\n return null;\n },\n\n matches: (fieldName, message = 'Fields do not match') => (value, formData) => {\n if (value !== formData[fieldName]) {\n return message;\n }\n return null;\n },\n\n oneOf: (options, message = 'Invalid option') => (value) => {\n if (value && !options.includes(value)) {\n return message;\n }\n return null;\n },\n\n custom: (fn, message = 'Validation failed') => (value, formData) => {\n if (!fn(value, formData)) {\n return message;\n }\n return null;\n }\n};\n\n/**\n * Form Validator\n * Manages form validation state\n */\nexport class FormValidator {\n constructor(schema = {}) {\n this.schema = schema;\n this.errors = {};\n this.touched = {};\n }\n\n /**\n * Validate a single field\n */\n validateField(name, value, formData = {}) {\n const fieldValidators = this.schema[name];\n \n if (!fieldValidators) {\n return null;\n }\n\n const validatorArray = Array.isArray(fieldValidators) ? fieldValidators : [fieldValidators];\n\n for (const validator of validatorArray) {\n const error = validator(value, formData);\n if (error) {\n return error;\n }\n }\n\n return null;\n }\n\n /**\n * Validate entire form\n */\n validate(formData) {\n const errors = {};\n let isValid = true;\n\n for (const [name, value] of Object.entries(formData)) {\n const error = this.validateField(name, value, formData);\n if (error) {\n errors[name] = error;\n isValid = false;\n }\n }\n\n // Check for required fields not in formData\n for (const name of Object.keys(this.schema)) {\n if (!(name in formData)) {\n const error = this.validateField(name, undefined, formData);\n if (error) {\n errors[name] = error;\n isValid = false;\n }\n }\n }\n\n this.errors = errors;\n return { isValid, errors };\n }\n\n /**\n * Mark field as touched\n */\n touch(name) {\n this.touched[name] = true;\n }\n\n /**\n * Check if field is touched\n */\n isTouched(name) {\n return this.touched[name] || false;\n }\n\n /**\n * Get error for field\n */\n getError(name) {\n return this.errors[name] || null;\n }\n\n /**\n * Check if field has error\n */\n hasError(name) {\n return !!this.errors[name];\n }\n\n /**\n * Clear errors\n */\n clearErrors() {\n this.errors = {};\n }\n\n /**\n * Clear touched state\n */\n clearTouched() {\n this.touched = {};\n }\n\n /**\n * Reset validator\n */\n reset() {\n this.clearErrors();\n this.clearTouched();\n }\n}\n\n/**\n * Create a form validator\n */\nexport function createValidator(schema) {\n return new FormValidator(schema);\n}\n\n/**\n * Validate form data against schema\n */\nexport function validate(formData, schema) {\n const validator = new FormValidator(schema);\n return validator.validate(formData);\n}\n\nexport default {\n validators,\n FormValidator,\n createValidator,\n validate\n};\n", "/**\n * Coherent.js Forms - Validators\n *\n * Form validation utilities\n *\n * @module forms/validators\n */\n\nimport { validators as packageValidators } from './validation.js';\nimport { isEmailShaped } from './patterns.js';\n\n/**\n * Built-in validators with signature: (value, options, translator, allValues) => errorMessage | null\n */\nexport const validators = {\n required: (value, options = {}) => {\n if (value === null || value === undefined || value === '') {\n return options.message || validators.required.message || 'This field is required';\n }\n return null;\n },\n\n email: (value) => {\n if (!value) return null;\n if (!isEmailShaped(value)) {\n return 'Please enter a valid email address';\n }\n return null;\n },\n\n minLength: (value, options = {}) => {\n if (!value) return null;\n const min = options.min || 0;\n if (value.length < min) {\n return options.message || `Must be at least ${min} characters`;\n }\n return null;\n },\n\n maxLength: (value, options = {}) => {\n if (!value) return null;\n const max = options.max || Infinity;\n if (value.length > max) {\n return options.message || `Must be no more than ${max} characters`;\n }\n return null;\n },\n\n min: (value, options = {}) => {\n if (value === null || value === undefined || value === '') return null;\n const num = Number(value);\n const minValue = options.min || 0;\n if (isNaN(num) || num < minValue) {\n return options.message || `Must be at least ${minValue}`;\n }\n return null;\n },\n\n max: (value, options = {}) => {\n if (value === null || value === undefined || value === '') return null;\n const num = Number(value);\n const maxValue = options.max || Infinity;\n if (isNaN(num) || num > maxValue) {\n return options.message || `Must be no more than ${maxValue}`;\n }\n return null;\n },\n\n pattern: (value, options = {}) => {\n if (!value) return null;\n const regex = options.pattern || options.regex;\n if (regex && !regex.test(value)) {\n return options.message || 'Invalid format';\n }\n return null;\n },\n\n url: (value) => {\n if (!value) return null;\n try {\n new URL(value);\n return null;\n } catch {\n return 'Please enter a valid URL';\n }\n },\n\n number: (value) => {\n if (value === null || value === undefined || value === '') return null;\n if (isNaN(Number(value))) {\n return 'Must be a valid number';\n }\n return null;\n },\n\n integer: (value) => {\n if (value === null || value === undefined || value === '') return null;\n const num = Number(value);\n if (isNaN(num) || !Number.isInteger(num)) {\n return 'Must be a whole number';\n }\n return null;\n },\n\n phone: (value) => {\n if (!value) return null;\n const phoneRegex = /^[\\d\\s\\-\\+\\(\\)]+$/;\n if (!phoneRegex.test(value) || value.replace(/\\D/g, '').length < 10) {\n return 'Please enter a valid phone number';\n }\n return null;\n },\n\n date: (value) => {\n if (!value) return null;\n const date = new Date(value);\n if (isNaN(date.getTime())) {\n return 'Please enter a valid date';\n }\n return null;\n },\n\n match: (value, options = {}, translator, allValues = {}) => {\n if (!value) return null;\n const fieldName = options.field || options.fieldName;\n if (value !== allValues[fieldName]) {\n return options.message || `Must match ${fieldName}`;\n }\n return null;\n },\n\n custom: (value, options = {}, translator, allValues) => {\n const validatorFn = options.validator || options.fn;\n if (!validatorFn) return null;\n const isValid = validatorFn(value, allValues);\n return isValid ? null : (options.message || 'Validation failed');\n },\n\n fileType: (value, options = {}) => {\n if (!value) return null;\n \n const allowedTypes = options.accept || options.types || [];\n \n // Handle File object\n if (value.type !== undefined) {\n const fileType = value.type;\n const fileExt = value.name ? value.name.split('.').pop().toLowerCase() : '';\n \n // Check MIME type or extension\n const isValid = allowedTypes.some(type => {\n if (type.startsWith('.')) {\n return fileExt === type.slice(1).toLowerCase();\n }\n if (type.includes('/')) {\n if (type.endsWith('/*')) {\n return fileType.startsWith(type.replace('/*', '/'));\n }\n return fileType === type;\n }\n return fileExt === type.toLowerCase();\n });\n \n if (!isValid) {\n return options.message || `File type must be one of: ${allowedTypes.join(', ')}`;\n }\n return null;\n }\n \n return null;\n },\n\n fileSize: (value, options = {}) => {\n if (!value) return null;\n \n const maxSize = options.maxSize || Infinity;\n \n // Handle File object\n if (value.size !== undefined) {\n if (value.size > maxSize) {\n const maxSizeMB = (maxSize / (1024 * 1024)).toFixed(2);\n return options.message || `File size must be less than ${maxSizeMB}MB`;\n }\n return null;\n }\n \n return null;\n },\n\n fileExtension: (value, options = {}) => {\n if (!value) return null;\n \n const allowedExtensions = options.extensions || [];\n const fileName = value.name || value;\n const ext = `.${ fileName.split('.').pop().toLowerCase()}`;\n \n const isValid = allowedExtensions.some(allowed => {\n return ext === allowed.toLowerCase();\n });\n \n if (!isValid) {\n return options.message || `File extension must be one of: ${allowedExtensions.join(', ')}`;\n }\n return null;\n },\n\n alpha: (value) => {\n if (!value) return null;\n const alphaRegex = /^[a-zA-Z]+$/;\n if (!alphaRegex.test(value)) {\n return 'Must contain only letters';\n }\n return null;\n },\n\n alphanumeric: (value) => {\n if (!value) return null;\n const alphanumericRegex = /^[a-zA-Z0-9]+$/;\n if (!alphanumericRegex.test(value)) {\n return 'Must contain only letters and numbers';\n }\n return null;\n },\n\n uppercase: (value) => {\n if (!value) return null;\n if (value !== value.toUpperCase()) {\n return 'Must be uppercase';\n }\n return null;\n },\n\n // Get a registered validator\n get: (name) => {\n return validators[name];\n },\n\n // Compose multiple validators\n compose: (validatorList) => {\n return (value, options, translator, allValues) => {\n for (const validator of validatorList) {\n const error = typeof validator === 'function'\n ? validator(value, options, translator, allValues)\n : null;\n if (error) {\n return error;\n }\n }\n return null;\n };\n },\n\n // Debounce async validator\n debounce: (validator, delay = 300) => {\n let timeoutId;\n return (value) => {\n return new Promise((resolve) => {\n clearTimeout(timeoutId);\n timeoutId = setTimeout(async () => {\n const result = await validator(value);\n resolve(result);\n }, delay);\n });\n };\n },\n\n // Cancellable async validator\n cancellable: (validator) => {\n let abortController;\n const wrapped = async (value) => {\n if (abortController) {\n abortController.abort();\n }\n // AbortController is a global browser/Node.js API\n abortController = typeof AbortController !== 'undefined' ? new AbortController() : null;\n try {\n return await validator(value, abortController ? abortController.signal : null);\n } catch (error) {\n if (error.name === 'AbortError') {\n return null;\n }\n throw error;\n }\n };\n wrapped.cancel = () => {\n if (abortController) {\n abortController.abort();\n }\n };\n return wrapped;\n },\n\n // Conditional validator\n when: (condition, validator) => {\n return (value, options = {}, translator, allValues = {}) => {\n // Pass options as context if it looks like context (has non-validator properties)\n const context = options.min !== undefined || options.max !== undefined ? allValues : options;\n const shouldValidate = typeof condition === 'function' \n ? condition(value, context) \n : condition;\n \n if (!shouldValidate) {\n return null;\n }\n \n return typeof validator === 'function'\n ? validator(value, options, translator, allValues)\n : null;\n };\n },\n\n // Validator chain builder\n chain: (options = {}) => {\n const validatorList = [];\n const stopOnFirstError = options.stopOnFirstError !== false;\n \n const chain = {\n required: (opts) => {\n validatorList.push((v, o, t, a) => validators.required(v, opts || o, t, a));\n return chain;\n },\n email: (opts) => {\n validatorList.push((v, o, t, a) => validators.email(v, opts || o, t, a));\n return chain;\n },\n minLength: (opts) => {\n validatorList.push((v, o, t, a) => validators.minLength(v, opts || o, t, a));\n return chain;\n },\n maxLength: (opts) => {\n validatorList.push((v, o, t, a) => validators.maxLength(v, opts || o, t, a));\n return chain;\n },\n custom: (fn, message) => {\n validatorList.push((v, o, t, a) => {\n // Custom validator returns null if valid, message if invalid\n const result = fn(v, a);\n return result === null || result === true || result === undefined ? null : (message || result);\n });\n return chain;\n },\n validate: (value, opts, translator, allValues) => {\n if (stopOnFirstError) {\n // Stop on first error - return single error or null\n for (const validator of validatorList) {\n const error = validator(value, opts, translator, allValues);\n if (error) {\n return error;\n }\n }\n return null;\n } else {\n // Collect all errors - return array or null\n const errors = [];\n for (const validator of validatorList) {\n const error = validator(value, opts, translator, allValues);\n if (error) {\n errors.push(error);\n }\n }\n return errors.length > 0 ? errors : null;\n }\n }\n };\n \n return chain;\n }\n};\n\n/**\n * Validate a single field\n */\nexport function validateField(value, validatorList, formData = {}) {\n for (const validator of validatorList) {\n const error = validator(value, formData);\n if (error) {\n return error;\n }\n }\n return null;\n}\n\n/**\n * Validate entire form\n */\nexport function validateForm(formData, fieldValidators) {\n const errors = {};\n \n for (const [fieldName, validatorList] of Object.entries(fieldValidators)) {\n const value = formData[fieldName];\n const error = validateField(value, validatorList, formData);\n if (error) {\n errors[fieldName] = error;\n }\n }\n \n return Object.keys(errors).length > 0 ? errors : null;\n}\n\n/**\n * Create a validator\n */\nexport function createValidator(validatorFn, message) {\n return (value, options, translator, allValues) => {\n const result = validatorFn(value, options, translator, allValues);\n // If validator returns a string, use it as the error message\n if (typeof result === 'string') {\n return result;\n }\n // If validator returns falsy (null, false, undefined), no error\n if (!result) {\n return null;\n }\n // If validator returns truthy (true, object, etc), use provided message\n return message || 'Validation failed';\n };\n}\n\n/**\n * Register a custom validator\n *\n * index.js re-exports validation.js's `validators`, which shadows this\n * module's object in the star export \u2014 so registering only here left\n * `validators[name]` undefined for every consumer. Register in both: the\n * package registry so callers can reach it, and the local one so `get()` and\n * `compose()` keep resolving it by name.\n */\nexport function registerValidator(name, validatorFn) {\n validators[name] = validatorFn;\n packageValidators[name] = validatorFn;\n}\n\n/**\n * Compose multiple validators\n */\nexport function composeValidators(...validatorFns) {\n return (value, options, translator, allValues) => {\n for (const validator of validatorFns) {\n const error = validator(value, options, translator, allValues);\n if (error) {\n return error;\n }\n }\n return null;\n };\n}\n\nexport default {\n validators,\n validateField,\n validateForm,\n createValidator,\n registerValidator,\n composeValidators\n};\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": [
|
|
3
|
+
"sources": ["../src/patterns.js", "../src/rules.js", "../src/validation.js", "../src/validators.js"],
|
|
4
|
+
"sourcesContent": ["/**\n * Shared validation patterns.\n *\n * Internal to @coherent.js/forms \u2014 not re-exported from index.js.\n */\n\n/**\n * Email shape check: a local part, then a dotted domain.\n *\n * Domain labels use `[^\\s@.]` rather than `[^\\s@]` so that the literal dot\n * separators are the only thing that can match a dot. Allowing `[^\\s@]+` on\n * both sides of `\\.` makes the split ambiguous, and a non-matching subject\n * with many dots (\"a@\" + \"a.\" * n + \" \") then costs O(n\u00B2) backtracking \u2014\n * CodeQL js/polynomial-redos.\n *\n * This is a shape check, not RFC 5322 conformance. Deliverability is only\n * ever established by sending mail.\n */\nexport const EMAIL_PATTERN = /^[^\\s@]+@[^\\s@.]+(?:\\.[^\\s@.]+)+$/;\n\n/**\n * Longest address RFC 5321 permits, used to bound work before matching.\n */\nexport const EMAIL_MAX_LENGTH = 254;\n\n/**\n * Test whether a value has the shape of an email address.\n *\n * @param {unknown} value - Value to check\n * @returns {boolean} True if the value looks like an email address\n */\nexport function isEmailShaped(value) {\n return (\n typeof value === 'string' &&\n value.length <= EMAIL_MAX_LENGTH &&\n EMAIL_PATTERN.test(value)\n );\n}\n", "/**\n * Coherent.js Forms - validation rules\n *\n * The single `validators` registry behind `@coherent.js/forms`,\n * `@coherent.js/forms/validation` and `@coherent.js/forms/validators`.\n * Internal: import it through one of those entry points.\n *\n * ## Calling convention\n *\n * A **validator** is `(value, formData) => string | null`: an error message,\n * or `null` when the value passes. Schemas, `validateField`, `validateForm`,\n * `FormBuilder` fields and `hydrateForm` all run validators this way.\n *\n * Each built-in is a **factory** that returns a validator:\n *\n * validators.required() validators.required('Required!')\n * validators.minLength(8) validators.minLength(8, 'Too short')\n *\n * A built-in may also be listed without calling it (`[validators.required]`);\n * runners call the factory with its defaults first. A string names a built-in\n * or registered validator, and a built-in's name may carry its arguments:\n * `'minLength:8'` is `validators.minLength(8)` (see argsFromString).\n *\n * For backward compatibility a built-in can still be called **directly** with\n * a value and an options object, returning the error or `null`:\n *\n * validators.minLength('abc', { min: 5 }) // 'Minimum length is 5'\n * validators.email(null) // null\n *\n * A lone argument that could be a message \u2014 a non-empty string, `undefined`\n * or `null` \u2014 is always read as a factory call, so `validators.email('a@b.c')`\n * returns a validator; call `validators.email('a@b.c', {})` (or\n * `validators.email()('a@b.c')`) to check a value directly.\n *\n * @module forms/rules\n */\n\nimport { isEmailShaped } from './patterns.js';\n\n/** Built-in factories, so runners can tell them from plain validators. */\nconst BUILTIN = Symbol('coherent.forms.builtin');\n\n/**\n * Validators whose origin is known, so the form builder can describe them to\n * the client: validator \u2192 `{ name, args }`.\n */\nconst DESCRIPTORS = new WeakMap();\n\n/** Names on `validators` that are helpers, not validation rules. */\nconst HELPER_NAMES = new Set(['get', 'compose', 'debounce', 'cancellable', 'when', 'chain']);\n\nconst isEmpty = value => value === null || value === undefined || value === '';\n\nconst isOptionsObject = value =>\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n !(value instanceof RegExp);\n\n/**\n * Factory arguments for the text after the colon of a `'name:\u2026'` string\n * entry, or `null` when it does not fit the rule.\n *\n * - No parameter (`required`, `email`, \u2026): the text is the message \u2014\n * `'required:Please enter your name'`.\n * - `'list'` (`oneOf`, `fileType`, `fileExtension`): every comma-separated\n * value goes into the one array parameter \u2014 `'oneOf:red,green,blue'`.\n * - `'regexp'` (`pattern`): the whole text is the regular expression source,\n * commas included \u2014 `'pattern:^[a-z]{2,8}$'`.\n * - Otherwise the text up to the first comma is the parameter (a number for\n * `'number'`, so `'minLength:abc'` is rejected) and the rest, commas\n * included, is the message \u2014 `'minLength:8,Use at least 8 characters'`.\n *\n * @param {Array} params - The rule's factory parameters\n * @param {'string'|'number'|'list'|'regexp'} kind - How the text supplies them\n * @param {string|undefined} text - Text after the colon (`undefined`: none)\n * @returns {Array|null}\n */\nfunction argsFromString(params, kind, text) {\n if (text === undefined || text.trim() === '') return [];\n if (params.length === 0) return [text.trim()];\n\n if (kind === 'list') {\n return [text.split(',').map(part => part.trim()).filter(Boolean)];\n }\n if (kind === 'regexp') {\n try {\n return [new RegExp(text)];\n } catch {\n return null;\n }\n }\n\n const comma = text.indexOf(',');\n const raw = (comma === -1 ? text : text.slice(0, comma)).trim();\n const message = comma === -1 ? '' : text.slice(comma + 1).trim();\n\n let arg = raw;\n if (kind === 'number') {\n arg = Number(raw);\n if (raw === '' || Number.isNaN(arg)) return null;\n }\n return message ? [arg, message] : [arg];\n}\n\n/**\n * Define a built-in rule.\n *\n * @param {string} name - Registry name\n * @param {Object} spec\n * @param {Array<[string, ...string[]]>} [spec.params] - Factory parameters, in\n * order, each as `[configKey, ...optionAliases]` for direct calls\n * @param {'string'|'number'|'list'|'regexp'} [spec.stringArg] - How the\n * `'name:\u2026'` string form supplies the parameter (see argsFromString)\n * @param {(value: unknown, config: Object, formData: Object) => boolean} spec.valid\n * @param {(config: Object) => string} spec.message - Default error message\n */\nfunction defineRule(name, { params = [], stringArg = 'string', valid, message }) {\n const fromOptions = (options = {}) => {\n const config = { message: options.message };\n for (const [key, ...aliases] of params) {\n config[key] = [key, ...aliases].map(alias => options[alias]).find(v => v !== undefined);\n }\n return config;\n };\n\n const check = (value, config, formData) => {\n if (valid(value, config, formData || {})) return null;\n return config.message || builtin.message || message(config);\n };\n\n const factory = (...args) => {\n const config = { message: args[params.length] };\n params.forEach(([key], index) => { config[key] = args[index]; });\n\n // Also accepts the (value, options, translator, allValues) arguments of\n // the older direct convention, so it composes with either.\n const rule = (value, formData, _translator, allValues) =>\n check(value, config, allValues ?? formData);\n\n DESCRIPTORS.set(rule, { name, args });\n return rule;\n };\n\n // Direct calls pass a value plus an options object. Anything else is a\n // factory call, including `required(undefined)` (a message that happens to\n // be missing) \u2014 only an explicit options object, or a lone argument that\n // cannot be a message, selects the direct form.\n const isDirectCall = (args) => {\n if (args.length >= 2) {\n return params.length === 0 || isOptionsObject(args[1]);\n }\n if (args.length === 1 && params.length === 0) {\n const [arg] = args;\n return arg === '' || (arg !== undefined && arg !== null && typeof arg !== 'string');\n }\n return false;\n };\n\n function builtin(...args) {\n if (isDirectCall(args)) {\n const [value, options, , allValues] = args;\n return check(value, fromOptions(isOptionsObject(options) ? options : {}), allValues);\n }\n return factory(...args);\n }\n\n Object.defineProperty(builtin, 'name', { value: name });\n builtin[BUILTIN] = {\n name,\n factory,\n argsFromString: (text) => argsFromString(params, stringArg, text)\n };\n return builtin;\n}\n\nconst toNumber = value => (typeof value === 'number' ? value : Number(value));\n\nfunction testRegExp(regex, value) {\n if (!(regex instanceof RegExp)) return true;\n // A /g or /y regex keeps lastIndex between calls; always test from the start.\n regex.lastIndex = 0;\n return regex.test(String(value));\n}\n\nfunction fileMatchesType(file, allowedTypes) {\n const fileType = file.type;\n const fileExt = file.name ? file.name.split('.').pop().toLowerCase() : '';\n\n return allowedTypes.some(type => {\n if (type.startsWith('.')) {\n return fileExt === type.slice(1).toLowerCase();\n }\n if (type.includes('/')) {\n if (type.endsWith('/*')) {\n return fileType.startsWith(type.replace('/*', '/'));\n }\n return fileType === type;\n }\n return fileExt === type.toLowerCase();\n });\n}\n\n/**\n * Built-in validators and helpers. See the module comment for the calling\n * convention.\n */\nexport const validators = {\n required: defineRule('required', {\n valid: value => !isEmpty(value),\n message: () => 'This field is required'\n }),\n\n email: defineRule('email', {\n valid: value => isEmpty(value) || isEmailShaped(value),\n message: () => 'Invalid email address'\n }),\n\n url: defineRule('url', {\n valid: (value) => {\n if (isEmpty(value)) return true;\n try {\n new URL(value);\n return true;\n } catch {\n return false;\n }\n },\n message: () => 'Invalid URL'\n }),\n\n minLength: defineRule('minLength', {\n params: [['min', 'minLength']],\n stringArg: 'number',\n valid: (value, { min }) => !value || !(value.length < (min ?? 0)),\n message: ({ min }) => `Minimum length is ${min}`\n }),\n\n maxLength: defineRule('maxLength', {\n params: [['max', 'maxLength']],\n stringArg: 'number',\n valid: (value, { max }) => !value || !(value.length > (max ?? Infinity)),\n message: ({ max }) => `Maximum length is ${max}`\n }),\n\n // Empty values pass (combine with `required`); a non-numeric value fails.\n min: defineRule('min', {\n params: [['min']],\n stringArg: 'number',\n valid: (value, { min }) => {\n if (isEmpty(value)) return true;\n const number = toNumber(value);\n return !Number.isNaN(number) && !(number < (min ?? -Infinity));\n },\n message: ({ min }) => `Minimum value is ${min}`\n }),\n\n max: defineRule('max', {\n params: [['max']],\n stringArg: 'number',\n valid: (value, { max }) => {\n if (isEmpty(value)) return true;\n const number = toNumber(value);\n return !Number.isNaN(number) && !(number > (max ?? Infinity));\n },\n message: ({ max }) => `Maximum value is ${max}`\n }),\n\n pattern: defineRule('pattern', {\n params: [['pattern', 'regex']],\n stringArg: 'regexp',\n valid: (value, { pattern }) => isEmpty(value) || testRegExp(pattern, value),\n message: () => 'Invalid format'\n }),\n\n /** Equal to another field, even when empty */\n matches: defineRule('matches', {\n params: [['field', 'fieldName']],\n valid: (value, { field }, formData) => value === formData[field],\n message: () => 'Fields do not match'\n }),\n\n /** Equal to another field; an empty value passes */\n match: defineRule('match', {\n params: [['field', 'fieldName']],\n valid: (value, { field }, formData) => !value || value === formData[field],\n message: ({ field }) => `Must match ${field}`\n }),\n\n oneOf: defineRule('oneOf', {\n params: [['options', 'values']],\n stringArg: 'list',\n valid: (value, { options }) => !value || !Array.isArray(options) || options.includes(value),\n message: () => 'Invalid option'\n }),\n\n custom: defineRule('custom', {\n params: [['validator', 'fn']],\n valid: (value, { validator }, formData) =>\n typeof validator !== 'function' || Boolean(validator(value, formData)),\n message: () => 'Validation failed'\n }),\n\n number: defineRule('number', {\n valid: value => isEmpty(value) || !Number.isNaN(Number(value)),\n message: () => 'Must be a valid number'\n }),\n\n integer: defineRule('integer', {\n valid: value => isEmpty(value) || Number.isInteger(Number(value)),\n message: () => 'Must be a whole number'\n }),\n\n phone: defineRule('phone', {\n valid: value => !value || (/^[\\d\\s\\-+()]+$/.test(value) && value.replace(/\\D/g, '').length >= 10),\n message: () => 'Please enter a valid phone number'\n }),\n\n date: defineRule('date', {\n valid: value => !value || !Number.isNaN(new Date(value).getTime()),\n message: () => 'Please enter a valid date'\n }),\n\n alpha: defineRule('alpha', {\n valid: value => !value || /^[a-zA-Z]+$/.test(value),\n message: () => 'Must contain only letters'\n }),\n\n alphanumeric: defineRule('alphanumeric', {\n valid: value => !value || /^[a-zA-Z0-9]+$/.test(value),\n message: () => 'Must contain only letters and numbers'\n }),\n\n uppercase: defineRule('uppercase', {\n valid: value => !value || value === String(value).toUpperCase(),\n message: () => 'Must be uppercase'\n }),\n\n fileType: defineRule('fileType', {\n params: [['accept', 'types']],\n stringArg: 'list',\n valid: (value, { accept }) =>\n !value || value.type === undefined || fileMatchesType(value, accept || []),\n message: ({ accept }) => `File type must be one of: ${(accept || []).join(', ')}`\n }),\n\n fileSize: defineRule('fileSize', {\n params: [['maxSize']],\n stringArg: 'number',\n valid: (value, { maxSize }) =>\n !value || value.size === undefined || !(value.size > (maxSize ?? Infinity)),\n message: ({ maxSize }) => `File size must be less than ${((maxSize ?? Infinity) / (1024 * 1024)).toFixed(2)}MB`\n }),\n\n fileExtension: defineRule('fileExtension', {\n params: [['extensions']],\n stringArg: 'list',\n valid: (value, { extensions }) => {\n if (!value) return true;\n const fileName = value.name || value;\n const ext = `.${String(fileName).split('.').pop().toLowerCase()}`;\n return (extensions || []).some(allowed => ext === allowed.toLowerCase());\n },\n message: ({ extensions }) => `File extension must be one of: ${(extensions || []).join(', ')}`\n }),\n\n /** A registered validator or built-in by name */\n get: (name) => (Object.hasOwn(validators, name) ? validators[name] : undefined),\n\n /** Combine validators into one returning the first error */\n compose: (validatorList) => {\n const rules = validatorList.map(resolveValidator);\n return (value, options, translator, allValues) => {\n for (const rule of rules) {\n const error = rule ? rule(value, options, translator, allValues) : null;\n if (error) {\n return error;\n }\n }\n return null;\n };\n },\n\n /** Debounce an async validator */\n debounce: (validator, delay = 300) => {\n let timeoutId;\n return (value) => {\n return new Promise((resolve) => {\n clearTimeout(timeoutId);\n timeoutId = setTimeout(async () => {\n const result = await validator(value);\n resolve(result);\n }, delay);\n });\n };\n },\n\n /** Cancellable async validator */\n cancellable: (validator) => {\n let abortController;\n const wrapped = async (value) => {\n if (abortController) {\n abortController.abort();\n }\n // AbortController is a global browser/Node.js API\n abortController = typeof AbortController !== 'undefined' ? new AbortController() : null;\n try {\n return await validator(value, abortController ? abortController.signal : null);\n } catch (error) {\n if (error.name === 'AbortError') {\n return null;\n }\n throw error;\n }\n };\n wrapped.cancel = () => {\n if (abortController) {\n abortController.abort();\n }\n };\n return wrapped;\n },\n\n /** Run `validator` only when `condition` holds */\n when: (condition, validator) => {\n const rule = resolveValidator(validator);\n return (value, options = {}, translator, allValues = {}) => {\n // Pass options as context if it looks like context (has non-validator properties)\n const context = options.min !== undefined || options.max !== undefined ? allValues : options;\n const shouldValidate = typeof condition === 'function'\n ? condition(value, context)\n : condition;\n\n if (!shouldValidate) {\n return null;\n }\n\n return rule ? rule(value, options, translator, allValues) : null;\n };\n },\n\n /** Validator chain builder */\n chain: (options = {}) => {\n const validatorList = [];\n const stopOnFirstError = options.stopOnFirstError !== false;\n\n const direct = name => (opts) => {\n validatorList.push((v, o, t, a) => validators[name](v, opts || o || {}, t, a));\n return chain;\n };\n\n const chain = {\n required: direct('required'),\n email: direct('email'),\n minLength: direct('minLength'),\n maxLength: direct('maxLength'),\n custom: (fn, message) => {\n validatorList.push((v, o, t, a) => {\n // Custom validator returns null if valid, message if invalid\n const result = fn(v, a);\n return result === null || result === true || result === undefined ? null : (message || result);\n });\n return chain;\n },\n validate: (value, opts, translator, allValues) => {\n if (stopOnFirstError) {\n // Stop on first error - return single error or null\n for (const validator of validatorList) {\n const error = validator(value, opts, translator, allValues);\n if (error) {\n return error;\n }\n }\n return null;\n }\n // Collect all errors - return array or null\n const errors = [];\n for (const validator of validatorList) {\n const error = validator(value, opts, translator, allValues);\n if (error) {\n errors.push(error);\n }\n }\n return errors.length > 0 ? errors : null;\n }\n };\n\n return chain;\n }\n};\n\n/** The registry entry named `name`, if it is a validator (not a helper). */\nfunction lookup(name) {\n const found = Object.hasOwn(validators, name) && !HELPER_NAMES.has(name)\n ? validators[name]\n : null;\n return typeof found === 'function' ? found : null;\n}\n\n/**\n * Resolve a string entry: `'required'`, or `'name:arg1,arg2'` for a built-in\n * with arguments (`'minLength:8'`, `'oneOf:a,b,c'`, `'pattern:^\\\\d+$'`; see\n * argsFromString). A registered validator is named without arguments.\n *\n * @param {string} entry\n * @returns {{ name: string, args: Array, validator: Function }|null} `null`\n * for an unknown name or arguments that do not fit the rule\n */\nfunction resolveString(entry) {\n // An exact registry name wins, so a validator registered under a name that\n // contains a colon still resolves.\n const exact = lookup(entry);\n const colon = exact ? -1 : entry.indexOf(':');\n const name = colon === -1 ? entry : entry.slice(0, colon).trim();\n const found = exact ?? lookup(name);\n if (!found) return null;\n\n const text = colon === -1 ? undefined : entry.slice(colon + 1);\n if (!found[BUILTIN]) {\n return text === undefined ? { name, args: [], validator: found } : null;\n }\n\n const args = found[BUILTIN].argsFromString(text);\n if (!args) return null;\n return { name: found[BUILTIN].name, args, validator: found[BUILTIN].factory(...args) };\n}\n\n/**\n * Turn a schema entry into a validator: a built-in factory listed without\n * calling it (`validators.required`) is called with its defaults, a string\n * names a built-in or registered validator (`'required'`, or a built-in with\n * arguments: `'minLength:8'`), a function is used as is. Anything else \u2014 an\n * unknown name, or arguments that do not fit (`'minLength:abc'`) \u2014 is `null`\n * (skipped).\n *\n * @param {unknown} entry\n * @returns {Function|null}\n */\nexport function resolveValidator(entry) {\n if (typeof entry === 'string') {\n return resolveString(entry)?.validator ?? null;\n }\n if (typeof entry !== 'function') return null;\n return entry[BUILTIN] ? entry[BUILTIN].factory() : entry;\n}\n\n/**\n * Run validators against one value; the first error wins.\n */\nexport function validateField(value, validatorList, formData = {}) {\n const list = Array.isArray(validatorList) ? validatorList : [validatorList];\n for (const entry of list) {\n const validator = resolveValidator(entry);\n const error = validator ? validator(value, formData) : null;\n if (error) {\n return error;\n }\n }\n return null;\n}\n\n/**\n * Validate a whole form: `{ field: [validators] }`. Returns the first error\n * per failing field, or `null` when everything passes.\n */\nexport function validateForm(formData, fieldValidators) {\n const errors = {};\n\n for (const [fieldName, validatorList] of Object.entries(fieldValidators)) {\n const value = formData[fieldName];\n const error = validateField(value, validatorList, formData);\n if (error) {\n errors[fieldName] = error;\n }\n }\n\n return Object.keys(errors).length > 0 ? errors : null;\n}\n\n/**\n * Wrap a check function as a validator: a string result is the error, any\n * other truthy result becomes `message`, a falsy one passes.\n */\nexport function wrapValidator(validatorFn, message) {\n return (value, options, translator, allValues) => {\n const result = validatorFn(value, options, translator, allValues);\n // If validator returns a string, use it as the error message\n if (typeof result === 'string') {\n return result;\n }\n // If validator returns falsy (null, false, undefined), no error\n if (!result) {\n return null;\n }\n // If validator returns truthy (true, object, etc), use provided message\n return message || 'Validation failed';\n };\n}\n\n/**\n * Register a validator under `name`. It is stored as is (not as a factory),\n * so list it as `validators[name]`. Registered validators are described to\n * the client by name, so `hydrateForm` enforces them when the same name is\n * registered in the browser.\n */\nexport function registerValidator(name, validatorFn) {\n validators[name] = validatorFn;\n if (typeof validatorFn === 'function' && !validatorFn[BUILTIN]) {\n DESCRIPTORS.set(validatorFn, { name, args: [] });\n }\n}\n\n/**\n * Compose multiple validators\n */\nexport function composeValidators(...validatorFns) {\n return validators.compose(validatorFns);\n}\n\n// ---------------------------------------------------------------------------\n// Server \u2192 client description (`data-validators`)\n// ---------------------------------------------------------------------------\n\n/** JSON cannot hold a RegExp; tag it so the client can rebuild it. */\nfunction serializeArg(arg) {\n if (arg instanceof RegExp) return { $regexp: [arg.source, arg.flags] };\n return arg;\n}\n\nfunction reviveArg(arg) {\n if (isOptionsObject(arg) && Array.isArray(arg.$regexp)) {\n try {\n return new RegExp(arg.$regexp[0], arg.$regexp[1]);\n } catch {\n return undefined;\n }\n }\n return arg;\n}\n\n/** Whether JSON can carry a factory argument to the client unchanged. */\nfunction isSerializable(arg) {\n if (arg === undefined || arg === null) return true;\n if (arg instanceof RegExp) return true;\n if (Array.isArray(arg)) return arg.every(isSerializable);\n return ['string', 'number', 'boolean'].includes(typeof arg);\n}\n\n/**\n * Describe a schema entry as `{ name, args }` for the client, or `null` when\n * it cannot be rebuilt there (an anonymous function, or a factory argument\n * such as a function that JSON cannot carry). Those still run on the server.\n */\nexport function describeValidator(entry) {\n let descriptor;\n if (typeof entry === 'string') {\n // `'minLength:8'` is described like `validators.minLength(8)`.\n descriptor = resolveString(entry);\n } else if (typeof entry === 'function') {\n descriptor = entry[BUILTIN] ? { name: entry[BUILTIN].name, args: [] } : DESCRIPTORS.get(entry);\n }\n if (!descriptor || !descriptor.args.every(isSerializable)) return null;\n\n // Trailing undefined arguments (an omitted message) are just defaults.\n const args = [...descriptor.args];\n while (args.length > 0 && args[args.length - 1] === undefined) args.pop();\n return { name: descriptor.name, args: args.map(serializeArg) };\n}\n\n/**\n * Serialize a field's validators for its `data-validators` attribute:\n * a JSON array of `{ name, args }`, or `null` when none can be described.\n */\nexport function serializeValidators(validatorList) {\n const specs = (validatorList || []).map(describeValidator).filter(Boolean);\n return specs.length > 0 ? JSON.stringify(specs) : null;\n}\n\n/**\n * Rebuild validators from a `data-validators` attribute. Reads the JSON\n * written by {@link serializeValidators}, and the older comma-separated form\n * (`required,minLength:8`). Unknown names are skipped.\n */\nexport function parseValidators(attribute) {\n if (!attribute) return [];\n const text = String(attribute).trim();\n\n let specs;\n if (text.startsWith('[')) {\n try {\n specs = JSON.parse(text);\n } catch {\n return [];\n }\n if (!Array.isArray(specs)) return [];\n } else {\n specs = text.split(',').map((part) => {\n const [name, ...params] = part.trim().split(':');\n return { name, args: params.map(p => (p !== '' && !Number.isNaN(Number(p)) ? Number(p) : p)) };\n });\n }\n\n return specs.map((spec) => {\n if (!spec || typeof spec.name !== 'string') return null;\n const { name } = spec;\n if (!Object.hasOwn(validators, name) || HELPER_NAMES.has(name)) return null;\n\n const entry = validators[name];\n if (typeof entry !== 'function') return null;\n\n // Built-ins are rebuilt through their factory, never the direct form.\n const args = Array.isArray(spec.args) ? spec.args.map(reviveArg) : [];\n return entry[BUILTIN] ? entry[BUILTIN].factory(...args) : entry;\n }).filter(Boolean);\n}\n", "/**\n * Coherent.js Form Validation\n * \n * Comprehensive validation utilities for forms\n * \n * @module forms/validation\n */\n\nimport { validators, resolveValidator, wrapValidator } from './rules.js';\n\n/**\n * Built-in validators (the same registry `@coherent.js/forms/validators`\n * exports). Each is a factory returning a `(value, formData) => error | null`\n * validator: `validators.minLength(8)`. See rules.js for the full convention.\n */\nexport { validators };\n\n/**\n * Form Validator\n * Manages form validation state\n */\nexport class FormValidator {\n constructor(schema = {}) {\n this.schema = schema;\n this.errors = {};\n this.touched = {};\n }\n\n /**\n * Validate a single field\n */\n validateField(name, value, formData = {}) {\n const fieldValidators = this.schema[name];\n \n if (!fieldValidators) {\n return null;\n }\n\n const validatorArray = Array.isArray(fieldValidators) ? fieldValidators : [fieldValidators];\n\n for (const entry of validatorArray) {\n // Accepts `validators.required` as well as `validators.required()`.\n const validator = resolveValidator(entry);\n const error = validator ? validator(value, formData) : null;\n if (error) {\n return error;\n }\n }\n\n return null;\n }\n\n /**\n * Validate entire form\n */\n validate(formData) {\n const errors = {};\n let isValid = true;\n\n for (const [name, value] of Object.entries(formData)) {\n const error = this.validateField(name, value, formData);\n if (error) {\n errors[name] = error;\n isValid = false;\n }\n }\n\n // Check for required fields not in formData\n for (const name of Object.keys(this.schema)) {\n if (!(name in formData)) {\n const error = this.validateField(name, undefined, formData);\n if (error) {\n errors[name] = error;\n isValid = false;\n }\n }\n }\n\n this.errors = errors;\n return { isValid, errors };\n }\n\n /**\n * Mark field as touched\n */\n touch(name) {\n this.touched[name] = true;\n }\n\n /**\n * Check if field is touched\n */\n isTouched(name) {\n return this.touched[name] || false;\n }\n\n /**\n * Get error for field\n */\n getError(name) {\n return this.errors[name] || null;\n }\n\n /**\n * Check if field has error\n */\n hasError(name) {\n return !!this.errors[name];\n }\n\n /**\n * Clear errors\n */\n clearErrors() {\n this.errors = {};\n }\n\n /**\n * Clear touched state\n */\n clearTouched() {\n this.touched = {};\n }\n\n /**\n * Reset validator\n */\n reset() {\n this.clearErrors();\n this.clearTouched();\n }\n}\n\n/**\n * Create a validator.\n *\n * - `createValidator(schema)` returns a {@link FormValidator} for a\n * `{ field: validator | validator[] }` schema.\n * - `createValidator(fn, message?)` wraps a check function as a validator: a\n * string result is the error, another truthy result becomes `message`, a\n * falsy one passes.\n *\n * Both entry points (`@coherent.js/forms` and `@coherent.js/forms/validators`)\n * export this same function.\n */\nexport function createValidator(schemaOrFn, message) {\n if (typeof schemaOrFn === 'function') {\n return wrapValidator(schemaOrFn, message);\n }\n return new FormValidator(schemaOrFn);\n}\n\n/**\n * Validate form data against schema\n */\nexport function validate(formData, schema) {\n const validator = new FormValidator(schema);\n return validator.validate(formData);\n}\n\nexport default {\n validators,\n FormValidator,\n createValidator,\n validate\n};\n", "/**\n * Coherent.js Forms - Validators\n *\n * Form validation utilities. `validators` and `createValidator` here are the\n * same as the ones `@coherent.js/forms` exports, with one calling convention:\n * a validator is `(value, formData) => error | null`, and each built-in is a\n * factory returning one (`validators.minLength(8)`). A built-in may be listed\n * uncalled (`[validators.required]`) and still accepts the older direct form\n * `validators.minLength(value, { min: 8 })`. See rules.js for details.\n *\n * @module forms/validators\n */\n\nimport {\n validators,\n validateField,\n validateForm,\n registerValidator,\n composeValidators\n} from './rules.js';\nimport { createValidator } from './validation.js';\n\nexport {\n validators,\n validateField,\n validateForm,\n createValidator,\n registerValidator,\n composeValidators\n};\n\nexport default {\n validators,\n validateField,\n validateForm,\n createValidator,\n registerValidator,\n composeValidators\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coherent.js/forms",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-rc.0",
|
|
4
4
|
"description": "SSR + Hydration form system for Coherent.js applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
"types": "./types/index.d.ts",
|
|
10
10
|
"default": "./dist/index.js"
|
|
11
11
|
},
|
|
12
|
+
"./csrf": {
|
|
13
|
+
"types": "./types/csrf.d.ts",
|
|
14
|
+
"default": "./dist/csrf.js"
|
|
15
|
+
},
|
|
12
16
|
"./form-builder": "./dist/form-builder.js",
|
|
13
17
|
"./hydration": "./dist/form-hydration.js",
|
|
14
18
|
"./validation": "./dist/validation.js",
|
|
@@ -23,8 +27,7 @@
|
|
|
23
27
|
"author": "Coherent.js Team",
|
|
24
28
|
"license": "MIT",
|
|
25
29
|
"peerDependencies": {
|
|
26
|
-
"@coherent.js/
|
|
27
|
-
"@coherent.js/core": "^1.1.2"
|
|
30
|
+
"@coherent.js/core": "^2.0.0-rc.0"
|
|
28
31
|
},
|
|
29
32
|
"repository": {
|
|
30
33
|
"type": "git",
|
package/types/csrf.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coherent.js Forms - CSRF tokens (server only)
|
|
3
|
+
*
|
|
4
|
+
* Stateless, session-bound tokens signed with HMAC-SHA256. Uses `node:crypto`,
|
|
5
|
+
* so import it on the server only; the package root stays browser-safe.
|
|
6
|
+
*
|
|
7
|
+
* @module @coherent.js/forms/csrf
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Name of the hidden field `buildForm({ csrfToken })` renders: `'_csrf'`. */
|
|
11
|
+
export const CSRF_FIELD_NAME: '_csrf';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Create a token bound to `sessionId`, signed with `secret`.
|
|
15
|
+
* Throws a `TypeError` when the secret or session id is missing.
|
|
16
|
+
*/
|
|
17
|
+
export function createCsrfToken(
|
|
18
|
+
secret: string | Uint8Array,
|
|
19
|
+
sessionId: string,
|
|
20
|
+
options?: { /** Issue time in ms; defaults to `Date.now()` */ now?: number }
|
|
21
|
+
): string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Check a submitted token against the current session. Returns `false`
|
|
25
|
+
* (never throws) for a missing, malformed, forged, expired or other-session
|
|
26
|
+
* token; throws a `TypeError` only when `secret` is missing.
|
|
27
|
+
*/
|
|
28
|
+
export function verifyCsrfToken(
|
|
29
|
+
token: unknown,
|
|
30
|
+
secret: string | Uint8Array,
|
|
31
|
+
sessionId: string,
|
|
32
|
+
options?: {
|
|
33
|
+
/** Reject tokens older than this many milliseconds */
|
|
34
|
+
maxAge?: number;
|
|
35
|
+
/** Current time in ms; defaults to `Date.now()` */
|
|
36
|
+
now?: number;
|
|
37
|
+
}
|
|
38
|
+
): boolean;
|
|
39
|
+
|
|
40
|
+
declare const _default: {
|
|
41
|
+
CSRF_FIELD_NAME: typeof CSRF_FIELD_NAME;
|
|
42
|
+
createCsrfToken: typeof createCsrfToken;
|
|
43
|
+
verifyCsrfToken: typeof verifyCsrfToken;
|
|
44
|
+
};
|
|
45
|
+
export default _default;
|
package/types/index.d.ts
CHANGED
|
@@ -72,8 +72,8 @@ export interface FormField<T = unknown> {
|
|
|
72
72
|
value?: T;
|
|
73
73
|
/** Options for select/radio fields */
|
|
74
74
|
options?: SelectOption[];
|
|
75
|
-
/** Validation rules */
|
|
76
|
-
validators?:
|
|
75
|
+
/** Validation rules, run in order on the server and described to `hydrateForm` */
|
|
76
|
+
validators?: ValidatorEntry[];
|
|
77
77
|
/** Field-specific validation configuration */
|
|
78
78
|
validation?: FieldValidation<T>;
|
|
79
79
|
/**
|
|
@@ -184,15 +184,45 @@ export interface FormConfig {
|
|
|
184
184
|
validateOnChange?: boolean;
|
|
185
185
|
/** Validate a field when it loses focus; defaults to `true` */
|
|
186
186
|
validateOnBlur?: boolean;
|
|
187
|
+
/**
|
|
188
|
+
* Render a hidden input carrying this CSRF token, first in the form. Create
|
|
189
|
+
* it per session with `createCsrfToken` from `@coherent.js/forms/csrf` and
|
|
190
|
+
* pass it per render: `buildForm({ csrfToken })`.
|
|
191
|
+
*/
|
|
192
|
+
csrfToken?: string;
|
|
193
|
+
/** Name of the hidden CSRF input; defaults to `'_csrf'` */
|
|
194
|
+
csrfFieldName?: string;
|
|
187
195
|
[option: string]: unknown;
|
|
188
196
|
}
|
|
189
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Per-render state for `buildForm()`. Passing any of these renders from them
|
|
200
|
+
* alone: the builder's own values, errors and touched state are neither read
|
|
201
|
+
* nor changed, so one shared builder can render every request's state.
|
|
202
|
+
*/
|
|
203
|
+
export interface FormRenderState {
|
|
204
|
+
/** Values to render, merged over the fields' default values */
|
|
205
|
+
values?: Record<string, unknown>;
|
|
206
|
+
/** Errors to render, keyed by field name */
|
|
207
|
+
errors?: Record<string, string>;
|
|
208
|
+
/** Fields whose error is shown; defaults to every field in `errors` */
|
|
209
|
+
touched?: Record<string, boolean>;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Options for `buildForm()`: form configuration plus per-render state. */
|
|
213
|
+
export type BuildFormOptions = FormConfig & FormRenderState;
|
|
214
|
+
|
|
190
215
|
/**
|
|
191
216
|
* Accumulates fields and renders them as a Coherent component.
|
|
192
217
|
*
|
|
193
218
|
* Every mutator is chainable, and `build()`, `render()` and `buildForm()` all
|
|
194
219
|
* return the same node — `toHTML()` is that node rendered to a string.
|
|
195
220
|
*
|
|
221
|
+
* A builder holds the values, errors and touched state of one submission. On
|
|
222
|
+
* a server, keep the definition at module scope and call `fork()` per request
|
|
223
|
+
* (or render with `buildForm({ values, errors })`), so one user's submission
|
|
224
|
+
* never renders into another user's page.
|
|
225
|
+
*
|
|
196
226
|
* ```ts
|
|
197
227
|
* const form = new FormBuilder({ name: 'signup' })
|
|
198
228
|
* .field('email', { type: 'email', label: 'Email', required: true })
|
|
@@ -258,31 +288,40 @@ export class FormBuilder<T extends Record<string, unknown> = Record<string, unkn
|
|
|
258
288
|
setAction(action: string): this;
|
|
259
289
|
setMethod(method: 'get' | 'post' | (string & {})): this;
|
|
260
290
|
|
|
261
|
-
/**
|
|
262
|
-
|
|
291
|
+
/**
|
|
292
|
+
* A copy of the definition (fields, groups, options, handlers) with fresh
|
|
293
|
+
* values, errors and touched state — one per request on a server.
|
|
294
|
+
*/
|
|
295
|
+
fork(): FormBuilder<T>;
|
|
296
|
+
|
|
297
|
+
/** Build the form component; see {@link FormRenderState} for per-render state */
|
|
298
|
+
buildForm(options?: BuildFormOptions): CoherentNode;
|
|
263
299
|
/** Alias of {@link FormBuilder.buildForm} */
|
|
264
|
-
build(options?:
|
|
300
|
+
build(options?: BuildFormOptions): CoherentNode;
|
|
265
301
|
/** Alias of {@link FormBuilder.buildForm} */
|
|
266
|
-
render(options?:
|
|
302
|
+
render(options?: BuildFormOptions): CoherentNode;
|
|
267
303
|
/** The built form, rendered to an HTML string */
|
|
268
|
-
toHTML(options?:
|
|
304
|
+
toHTML(options?: BuildFormOptions): string;
|
|
269
305
|
|
|
270
306
|
/** Merge configured class names over {@link DEFAULT_CLASS_NAMES} */
|
|
271
307
|
resolveClassNames(overrides?: Partial<FormClassNames>): FormClassNames;
|
|
272
308
|
|
|
309
|
+
/** The state a render uses: the per-render state given, else the builder's own */
|
|
310
|
+
resolveRenderState(options?: FormRenderState): Required<FormRenderState>;
|
|
311
|
+
|
|
273
312
|
/** Build the node for one field, including its label and error */
|
|
274
|
-
buildField(name: keyof T & string, classNames?: FormClassNames): CoherentNode;
|
|
313
|
+
buildField(name: keyof T & string, classNames?: FormClassNames, state?: Required<FormRenderState>): CoherentNode;
|
|
275
314
|
/** Build one field's control */
|
|
276
|
-
buildInput(name: keyof T & string, classNames?: FormClassNames): CoherentNode | null;
|
|
315
|
+
buildInput(name: keyof T & string, classNames?: FormClassNames, state?: Required<FormRenderState>): CoherentNode | null;
|
|
277
316
|
/** Build one field's label */
|
|
278
317
|
buildLabel(name: keyof T & string, classNames?: FormClassNames): CoherentNode | null;
|
|
279
318
|
/** Build one field's error message, or `null` when it has none to show */
|
|
280
|
-
buildError(name: keyof T & string, classNames?: FormClassNames): CoherentNode | null;
|
|
319
|
+
buildError(name: keyof T & string, classNames?: FormClassNames, state?: Required<FormRenderState>): CoherentNode | null;
|
|
281
320
|
|
|
282
321
|
/** Copy of the current values */
|
|
283
322
|
serialize(): Partial<T>;
|
|
284
|
-
/** Whether a field's `showWhen`/`showIf` condition
|
|
285
|
-
isFieldVisible(name: keyof T & string): boolean;
|
|
323
|
+
/** Whether a field's `showWhen`/`showIf` condition holds for `values` (default: the current values) */
|
|
324
|
+
isFieldVisible(name: keyof T & string, values?: Record<string, unknown>): boolean;
|
|
286
325
|
/** Restore default values and clear errors and touched state */
|
|
287
326
|
reset(): this;
|
|
288
327
|
}
|
|
@@ -413,16 +452,30 @@ export interface ValidationErrors {
|
|
|
413
452
|
*
|
|
414
453
|
* The second argument is the whole form, so validators like
|
|
415
454
|
* `validators.matches` can compare fields.
|
|
455
|
+
*
|
|
456
|
+
* This is the one calling convention every runner uses: schemas,
|
|
457
|
+
* `validateField`, `validateForm`, `FormBuilder` fields and `hydrateForm`.
|
|
416
458
|
*/
|
|
417
459
|
export interface Validator {
|
|
418
460
|
(value: unknown, formData?: Record<string, unknown>): string | null;
|
|
419
461
|
}
|
|
420
462
|
|
|
463
|
+
/**
|
|
464
|
+
* What a validator list accepts: a {@link Validator}, a built-in listed
|
|
465
|
+
* without calling it (`validators.required`, run with its defaults), or the
|
|
466
|
+
* name of a built-in or registered validator. A built-in's name may carry its
|
|
467
|
+
* arguments after a colon: `'minLength:8'` (= `validators.minLength(8)`),
|
|
468
|
+
* `'minLength:8,Too short'`, `'oneOf:s,m,l'`, `'pattern:^[a-z]+$'`,
|
|
469
|
+
* `'required:Name please'`. Unknown names and arguments that do not fit the
|
|
470
|
+
* rule are skipped.
|
|
471
|
+
*/
|
|
472
|
+
export type ValidatorEntry = Validator | BuiltinValidator | string;
|
|
473
|
+
|
|
421
474
|
/**
|
|
422
475
|
* Per-field validators. A field maps to one validator or a list run in order,
|
|
423
476
|
* stopping at the first error.
|
|
424
477
|
*/
|
|
425
|
-
export type ValidationSchema = Record<string,
|
|
478
|
+
export type ValidationSchema = Record<string, ValidatorEntry | ValidatorEntry[]>;
|
|
426
479
|
|
|
427
480
|
/**
|
|
428
481
|
* Runs a {@link ValidationSchema} and tracks errors and touched fields.
|
|
@@ -473,9 +526,22 @@ export class FormValidator {
|
|
|
473
526
|
}
|
|
474
527
|
|
|
475
528
|
/**
|
|
476
|
-
* Create a
|
|
529
|
+
* Create a {@link FormValidator} for a schema.
|
|
477
530
|
*/
|
|
478
531
|
export function createValidator(schema?: ValidationSchema): FormValidator;
|
|
532
|
+
/**
|
|
533
|
+
* Wrap a check function as a {@link Validator}: a string result is the error,
|
|
534
|
+
* another truthy result becomes `message`, a falsy one passes.
|
|
535
|
+
*/
|
|
536
|
+
export function createValidator(
|
|
537
|
+
validatorFn: (
|
|
538
|
+
value: unknown,
|
|
539
|
+
options?: unknown,
|
|
540
|
+
translator?: unknown,
|
|
541
|
+
allValues?: Record<string, unknown>
|
|
542
|
+
) => unknown,
|
|
543
|
+
message?: string
|
|
544
|
+
): Validator;
|
|
479
545
|
|
|
480
546
|
/**
|
|
481
547
|
* Validate data against a schema with a throwaway validator
|
|
@@ -491,7 +557,7 @@ export function validate(
|
|
|
491
557
|
*/
|
|
492
558
|
export function validateField(
|
|
493
559
|
value: unknown,
|
|
494
|
-
validatorList:
|
|
560
|
+
validatorList: ValidatorEntry | ValidatorEntry[],
|
|
495
561
|
formData?: Record<string, unknown>
|
|
496
562
|
): string | null;
|
|
497
563
|
|
|
@@ -501,7 +567,7 @@ export function validateField(
|
|
|
501
567
|
*/
|
|
502
568
|
export function validateForm(
|
|
503
569
|
formData: Record<string, unknown>,
|
|
504
|
-
fieldValidators: Record<string,
|
|
570
|
+
fieldValidators: Record<string, ValidatorEntry | ValidatorEntry[]>
|
|
505
571
|
): ValidationErrors | null;
|
|
506
572
|
|
|
507
573
|
/**
|
|
@@ -510,54 +576,132 @@ export function validateForm(
|
|
|
510
576
|
* Unlike the built-ins, which are factories, this stores `validatorFn`
|
|
511
577
|
* directly — so use it as `validators[name]`, not `validators[name]()`.
|
|
512
578
|
* Registering over a built-in therefore changes that name's calling
|
|
513
|
-
* convention.
|
|
579
|
+
* convention. The form builder describes registered validators to the client
|
|
580
|
+
* by name, so `hydrateForm` enforces them when the browser registers the same
|
|
581
|
+
* name.
|
|
514
582
|
*/
|
|
515
583
|
export function registerValidator(name: string, validatorFn: Validator): void;
|
|
516
584
|
|
|
517
585
|
/**
|
|
518
586
|
* Combine validators into one that returns the first error, or `null`.
|
|
519
587
|
*/
|
|
520
|
-
export function composeValidators(...validatorFns:
|
|
588
|
+
export function composeValidators(...validatorFns: ValidatorEntry[]): Validator;
|
|
521
589
|
|
|
522
590
|
// ============================================================================
|
|
523
591
|
// Built-in Validators
|
|
524
592
|
// ============================================================================
|
|
525
593
|
|
|
594
|
+
/** Options for the direct form of a built-in: `validators.minLength(value, { min: 8 })`. */
|
|
595
|
+
export interface BuiltinOptions {
|
|
596
|
+
message?: string;
|
|
597
|
+
[option: string]: unknown;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* A built-in validator. Call it as a factory to get a {@link Validator}
|
|
602
|
+
* (`validators.required()`, `validators.required('Name please')`), list it
|
|
603
|
+
* uncalled to use its defaults, or call it directly with a value and an
|
|
604
|
+
* options object (`validators.required(value, {})`) to get the error.
|
|
605
|
+
*
|
|
606
|
+
* A lone string argument is always a factory message.
|
|
607
|
+
*/
|
|
608
|
+
export interface BuiltinValidator<Args extends unknown[] = []> {
|
|
609
|
+
(...args: [...Args, message?: string]): Validator;
|
|
610
|
+
(value: unknown, options: BuiltinOptions, translator?: unknown, allValues?: Record<string, unknown>): string | null;
|
|
611
|
+
/** Overrides the default message of this built-in */
|
|
612
|
+
message?: string;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/** A chain built with `validators.chain()`. */
|
|
616
|
+
export interface ValidatorChain {
|
|
617
|
+
required(options?: BuiltinOptions): ValidatorChain;
|
|
618
|
+
email(options?: BuiltinOptions): ValidatorChain;
|
|
619
|
+
minLength(options?: BuiltinOptions & { min?: number }): ValidatorChain;
|
|
620
|
+
maxLength(options?: BuiltinOptions & { max?: number }): ValidatorChain;
|
|
621
|
+
custom(fn: (value: unknown, allValues?: Record<string, unknown>) => unknown, message?: string): ValidatorChain;
|
|
622
|
+
/** First error (or every error with `stopOnFirstError: false`), or `null` */
|
|
623
|
+
validate(
|
|
624
|
+
value: unknown,
|
|
625
|
+
options?: unknown,
|
|
626
|
+
translator?: unknown,
|
|
627
|
+
allValues?: Record<string, unknown>
|
|
628
|
+
): string | string[] | null;
|
|
629
|
+
}
|
|
630
|
+
|
|
526
631
|
/**
|
|
527
|
-
*
|
|
528
|
-
*
|
|
529
|
-
* `validators.required`.
|
|
632
|
+
* The validator registry, shared by `@coherent.js/forms`,
|
|
633
|
+
* `@coherent.js/forms/validation` and `@coherent.js/forms/validators`.
|
|
530
634
|
*
|
|
531
|
-
*
|
|
532
|
-
*
|
|
635
|
+
* Each built-in is a factory returning a {@link Validator}:
|
|
636
|
+
* `validators.minLength(8)`, `validators.email('Bad email')`. Validators added
|
|
637
|
+
* with {@link registerValidator} are stored as bare validators.
|
|
533
638
|
*/
|
|
534
639
|
export const validators: {
|
|
535
640
|
/** Reject `null`, `undefined` and the empty string */
|
|
536
|
-
required
|
|
641
|
+
required: BuiltinValidator;
|
|
537
642
|
/** Validate email format; empty values pass */
|
|
538
|
-
email
|
|
643
|
+
email: BuiltinValidator;
|
|
644
|
+
/** Parseable as a URL; empty values pass */
|
|
645
|
+
url: BuiltinValidator;
|
|
539
646
|
/** Minimum length; empty values pass */
|
|
540
|
-
minLength
|
|
647
|
+
minLength: BuiltinValidator<[min: number]>;
|
|
541
648
|
/** Maximum length; empty values pass */
|
|
542
|
-
maxLength
|
|
543
|
-
/** Minimum numeric value */
|
|
544
|
-
min
|
|
545
|
-
/** Maximum numeric value */
|
|
546
|
-
max
|
|
547
|
-
/** Parseable as a URL; empty values pass */
|
|
548
|
-
url(message?: string): Validator;
|
|
649
|
+
maxLength: BuiltinValidator<[max: number]>;
|
|
650
|
+
/** Minimum numeric value; empty values pass, non-numeric values fail */
|
|
651
|
+
min: BuiltinValidator<[min: number]>;
|
|
652
|
+
/** Maximum numeric value; empty values pass, non-numeric values fail */
|
|
653
|
+
max: BuiltinValidator<[max: number]>;
|
|
549
654
|
/** Match a regular expression; empty values pass */
|
|
550
|
-
pattern
|
|
551
|
-
/** Equal another field's value */
|
|
552
|
-
matches
|
|
655
|
+
pattern: BuiltinValidator<[regex: RegExp]>;
|
|
656
|
+
/** Equal another field's value (even when empty) */
|
|
657
|
+
matches: BuiltinValidator<[fieldName: string]>;
|
|
658
|
+
/** Equal another field's value; empty values pass */
|
|
659
|
+
match: BuiltinValidator<[fieldName: string]>;
|
|
553
660
|
/** One of a fixed set; empty values pass */
|
|
554
|
-
oneOf
|
|
661
|
+
oneOf: BuiltinValidator<[options: unknown[]]>;
|
|
555
662
|
/** Fail when `fn` returns falsy */
|
|
556
|
-
custom(
|
|
557
|
-
|
|
558
|
-
|
|
663
|
+
custom: BuiltinValidator<[fn: (value: unknown, formData?: Record<string, unknown>) => unknown]>;
|
|
664
|
+
/** A number; empty values pass */
|
|
665
|
+
number: BuiltinValidator;
|
|
666
|
+
/** A whole number; empty values pass */
|
|
667
|
+
integer: BuiltinValidator;
|
|
668
|
+
/** Digits, spaces and `-+()`, with at least 10 digits; empty values pass */
|
|
669
|
+
phone: BuiltinValidator;
|
|
670
|
+
/** Parseable as a date; empty values pass */
|
|
671
|
+
date: BuiltinValidator;
|
|
672
|
+
/** Letters only; empty values pass */
|
|
673
|
+
alpha: BuiltinValidator;
|
|
674
|
+
/** Letters and digits only; empty values pass */
|
|
675
|
+
alphanumeric: BuiltinValidator;
|
|
676
|
+
/** All uppercase; empty values pass */
|
|
677
|
+
uppercase: BuiltinValidator;
|
|
678
|
+
/** File MIME type or extension (`'image/*'`, `'.pdf'`) */
|
|
679
|
+
fileType: BuiltinValidator<[accept: string[]]>;
|
|
680
|
+
/** File size in bytes */
|
|
681
|
+
fileSize: BuiltinValidator<[maxSize: number]>;
|
|
682
|
+
/** File extension (`'.pdf'`) */
|
|
683
|
+
fileExtension: BuiltinValidator<[extensions: string[]]>;
|
|
684
|
+
|
|
685
|
+
/** A registered validator or built-in by name */
|
|
686
|
+
get(name: string): Validator | BuiltinValidator<unknown[]> | undefined;
|
|
687
|
+
/** Combine validators into one returning the first error */
|
|
688
|
+
compose(validatorList: ValidatorEntry[]): Validator;
|
|
689
|
+
/** Debounce an async validator */
|
|
690
|
+
debounce<V>(validator: (value: V) => unknown, delay?: number): (value: V) => Promise<unknown>;
|
|
691
|
+
/** Wrap an async validator so each call aborts the previous one */
|
|
692
|
+
cancellable<V>(
|
|
693
|
+
validator: (value: V, signal: AbortSignal | null) => Promise<string | null>
|
|
694
|
+
): ((value: V) => Promise<string | null>) & { cancel(): void };
|
|
695
|
+
/** Run `validator` only when `condition` holds */
|
|
696
|
+
when(
|
|
697
|
+
condition: boolean | ((value: unknown, context: Record<string, unknown>) => unknown),
|
|
698
|
+
validator: ValidatorEntry
|
|
559
699
|
): Validator;
|
|
560
|
-
|
|
700
|
+
/** Build a validator chain */
|
|
701
|
+
chain(options?: { stopOnFirstError?: boolean }): ValidatorChain;
|
|
702
|
+
|
|
703
|
+
/** Validators added with {@link registerValidator} */
|
|
704
|
+
[name: string]: (...args: any[]) => any;
|
|
561
705
|
};
|
|
562
706
|
|
|
563
707
|
// ============================================================================
|