feat: support custom required validation messages
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ai",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/authz",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/authz — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
+11
-11
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/cli",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/cli — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -28,16 +28,16 @@
|
||||
"wrnexus": "./dist/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.2.22",
|
||||
"@wrnexus/router": "^0.2.22",
|
||||
"@wrnexus/csr": "^0.2.22",
|
||||
"@wrnexus/compiler": "^0.2.22",
|
||||
"@wrnexus/styles": "^0.2.22",
|
||||
"@wrnexus/dev-server": "^0.2.22",
|
||||
"@wrnexus/ui": "^0.2.22",
|
||||
"@wrnexus/validation": "^0.2.22",
|
||||
"@wrnexus/i18n": "^0.2.22",
|
||||
"@wrnexus/db": "^0.2.22"
|
||||
"@wrnexus/core": "^0.2.23",
|
||||
"@wrnexus/router": "^0.2.23",
|
||||
"@wrnexus/csr": "^0.2.23",
|
||||
"@wrnexus/compiler": "^0.2.23",
|
||||
"@wrnexus/styles": "^0.2.23",
|
||||
"@wrnexus/dev-server": "^0.2.23",
|
||||
"@wrnexus/ui": "^0.2.23",
|
||||
"@wrnexus/validation": "^0.2.23",
|
||||
"@wrnexus/i18n": "^0.2.23",
|
||||
"@wrnexus/db": "^0.2.23"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/compiler",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/compiler — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/core",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/core — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/csr",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/csr — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.2.22"
|
||||
"@wrnexus/core": "^0.2.23"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/db",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/db — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/dev-server",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/dev-server — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -25,18 +25,18 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.2.22",
|
||||
"@wrnexus/router": "^0.2.22",
|
||||
"@wrnexus/ssr": "^0.2.22",
|
||||
"@wrnexus/csr": "^0.2.22",
|
||||
"@wrnexus/compiler": "^0.2.22",
|
||||
"@wrnexus/styles": "^0.2.22",
|
||||
"@wrnexus/ui": "^0.2.22",
|
||||
"@wrnexus/validation": "^0.2.22",
|
||||
"@wrnexus/i18n": "^0.2.22",
|
||||
"@wrnexus/db": "^0.2.22",
|
||||
"@wrnexus/pubsub": "^0.2.22",
|
||||
"@wrnexus/uploader": "^0.2.22"
|
||||
"@wrnexus/core": "^0.2.23",
|
||||
"@wrnexus/router": "^0.2.23",
|
||||
"@wrnexus/ssr": "^0.2.23",
|
||||
"@wrnexus/csr": "^0.2.23",
|
||||
"@wrnexus/compiler": "^0.2.23",
|
||||
"@wrnexus/styles": "^0.2.23",
|
||||
"@wrnexus/ui": "^0.2.23",
|
||||
"@wrnexus/validation": "^0.2.23",
|
||||
"@wrnexus/i18n": "^0.2.23",
|
||||
"@wrnexus/db": "^0.2.23",
|
||||
"@wrnexus/pubsub": "^0.2.23",
|
||||
"@wrnexus/uploader": "^0.2.23"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/encryption",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/encryption — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/helpers",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
|
||||
"license": "MIT",
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.2.22"
|
||||
"@wrnexus/core": "^0.2.23"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/i18n",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/i18n — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.2.22"
|
||||
"@wrnexus/core": "^0.2.23"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/jwt",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/jwt — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/mobile",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/mobile — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/native": "^0.2.22"
|
||||
"@wrnexus/native": "^0.2.23"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/native",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/native — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/oauth",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/oauth — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/pubsub",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/pubsub — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/queue",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/queue — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/reactive",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/reactive — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/router",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/router — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -21,8 +21,8 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/compiler": "^0.2.22",
|
||||
"@wrnexus/core": "^0.2.22"
|
||||
"@wrnexus/compiler": "^0.2.23",
|
||||
"@wrnexus/core": "^0.2.23"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ssr",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/ssr — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.2.22"
|
||||
"@wrnexus/core": "^0.2.23"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/styles",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/styles — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/uploader": "^0.2.22"
|
||||
"@wrnexus/uploader": "^0.2.23"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/test",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/test — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/tracking",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/tracking — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ui",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/ui — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -22,7 +22,7 @@
|
||||
"./ui.css": "./ui.css"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.2.22"
|
||||
"@wrnexus/core": "^0.2.23"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/uploader",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/uploader — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
@@ -21,7 +21,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.2.22"
|
||||
"@wrnexus/core": "^0.2.23"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -35,6 +35,7 @@ import { v } from "@wrnexus/validation";
|
||||
Every field schema is chainable and shares these base methods:
|
||||
|
||||
- `min(n, message?)` / `max(n, message?)` — for strings, bounds the length; for numbers, bounds the value.
|
||||
- `required(message?)` — require a non-empty value and optionally replace the default `"Required"` message on both server and browser validation.
|
||||
- `optional()` — an empty/missing value passes instead of erroring `"Required"`.
|
||||
- `label(text)` — human label carried into the descriptor.
|
||||
- `default(value)` — value substituted when the field is absent (implies `optional`).
|
||||
@@ -121,8 +122,8 @@ Define a schema and validate an API body:
|
||||
import { v, parseBody } from "@wrnexus/validation";
|
||||
|
||||
export const signupSchema = v.object({
|
||||
email: v.string().trim().email(),
|
||||
password: v.string().min(8).max(200),
|
||||
email: v.string().required("Enter your email address").trim().email(),
|
||||
password: v.string().required("Enter your password").min(8).max(200),
|
||||
age: v.number().integer().min(13).max(120).optional(),
|
||||
role: v.string().oneOf(["user", "admin"]).default("user"),
|
||||
agree: v.boolean(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/validation",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/validation — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ai",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/authz",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/cli",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -280,6 +280,14 @@ const MIGRATIONS: Migration[] = [
|
||||
// Explicit no-op: updating @wrnexus/dev-server and helpers is sufficient.
|
||||
},
|
||||
},
|
||||
{
|
||||
version: "0.2.23",
|
||||
id: "custom-required-validation-message",
|
||||
description: "No project-file changes; custom required messages are provided at runtime",
|
||||
apply() {
|
||||
// Explicit no-op: updating @wrnexus/validation is sufficient.
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/** Release tooling uses this to require an explicit migration entry per version. */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/compiler",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/core",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/csr",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/db",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/dev-server",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/encryption",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/helpers",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/i18n",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/jwt",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/mobile",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/native",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/oauth",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/pubsub",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/queue",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/reactive",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/router",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ssr",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/styles",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/test",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/tracking",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ui",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/uploader",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -35,6 +35,7 @@ import { v } from "@wrnexus/validation";
|
||||
Every field schema is chainable and shares these base methods:
|
||||
|
||||
- `min(n, message?)` / `max(n, message?)` — for strings, bounds the length; for numbers, bounds the value.
|
||||
- `required(message?)` — require a non-empty value and optionally replace the default `"Required"` message on both server and browser validation.
|
||||
- `optional()` — an empty/missing value passes instead of erroring `"Required"`.
|
||||
- `label(text)` — human label carried into the descriptor.
|
||||
- `default(value)` — value substituted when the field is absent (implies `optional`).
|
||||
@@ -121,8 +122,8 @@ Define a schema and validate an API body:
|
||||
import { v, parseBody } from "@wrnexus/validation";
|
||||
|
||||
export const signupSchema = v.object({
|
||||
email: v.string().trim().email(),
|
||||
password: v.string().min(8).max(200),
|
||||
email: v.string().required("Enter your email address").trim().email(),
|
||||
password: v.string().required("Enter your password").min(8).max(200),
|
||||
age: v.number().integer().min(13).max(120).optional(),
|
||||
role: v.string().oneOf(["user", "admin"]).default("user"),
|
||||
agree: v.boolean(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/validation",
|
||||
"version": "0.2.22",
|
||||
"version": "0.2.23",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -22,6 +22,8 @@ export type RuleDescriptor =
|
||||
export interface FieldDescriptor {
|
||||
type: "string" | "number" | "boolean";
|
||||
optional?: boolean;
|
||||
/** Message used when a required field is empty. Defaults to "Required". */
|
||||
requiredMessage?: string;
|
||||
label?: string;
|
||||
/** Trim string input before validating. */
|
||||
trim?: boolean;
|
||||
@@ -103,13 +105,20 @@ export function checkField(
|
||||
): { value: unknown; error: string | null } {
|
||||
if (desc.type === "boolean") {
|
||||
const value = raw === true || raw === "true" || raw === "on";
|
||||
if (!desc.optional && !value) return { value, error: "Required" };
|
||||
if (!desc.optional && !value) {
|
||||
return { value, error: desc.requiredMessage || "Required" };
|
||||
}
|
||||
return { value, error: null };
|
||||
}
|
||||
|
||||
const pre = desc.trim && typeof raw === "string" ? raw.trim() : raw;
|
||||
const empty = pre === undefined || pre === null || pre === "";
|
||||
if (empty) return { value: undefined, error: desc.optional ? null : "Required" };
|
||||
if (empty) {
|
||||
return {
|
||||
value: undefined,
|
||||
error: desc.optional ? null : desc.requiredMessage || "Required",
|
||||
};
|
||||
}
|
||||
|
||||
let value: unknown;
|
||||
if (desc.type === "number") {
|
||||
@@ -133,6 +142,7 @@ type Refinement = { fn: (value: unknown) => boolean | string; message?: string }
|
||||
abstract class FieldSchema {
|
||||
abstract readonly type: "string" | "number" | "boolean";
|
||||
protected _optional = false;
|
||||
protected _requiredMessage?: string;
|
||||
protected _label?: string;
|
||||
protected _default?: unknown;
|
||||
protected rules: RuleDescriptor[] = [];
|
||||
@@ -142,6 +152,12 @@ abstract class FieldSchema {
|
||||
this._optional = true;
|
||||
return this;
|
||||
}
|
||||
/** Require a non-empty value and optionally replace the default message. */
|
||||
required(message = "Required"): this {
|
||||
this._optional = false;
|
||||
this._requiredMessage = message;
|
||||
return this;
|
||||
}
|
||||
label(label: string): this {
|
||||
this._label = label;
|
||||
return this;
|
||||
@@ -183,6 +199,7 @@ abstract class FieldSchema {
|
||||
return {
|
||||
type: this.type,
|
||||
optional: this._optional || undefined,
|
||||
requiredMessage: this._requiredMessage,
|
||||
label: this._label,
|
||||
rules: this.rules,
|
||||
};
|
||||
|
||||
@@ -42,11 +42,11 @@ export const VALIDATE_RUNTIME = String.raw`
|
||||
function checkField(desc, raw) {
|
||||
if (desc.type === "boolean") {
|
||||
var b = raw === true || raw === "true" || raw === "on";
|
||||
return (!desc.optional && !b) ? "Required" : null;
|
||||
return (!desc.optional && !b) ? (desc.requiredMessage || "Required") : null;
|
||||
}
|
||||
var pre = desc.trim && typeof raw === "string" ? raw.trim() : raw;
|
||||
var empty = pre === undefined || pre === null || pre === "";
|
||||
if (empty) return desc.optional ? null : "Required";
|
||||
if (empty) return desc.optional ? null : (desc.requiredMessage || "Required");
|
||||
var value;
|
||||
if (desc.type === "number") { value = Number(pre); if (isNaN(value)) return "Must be a number"; }
|
||||
else value = String(pre);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { v, invalid, parseBody, checkField, parseEnv } from "../src/index.ts";
|
||||
import { Window } from "happy-dom";
|
||||
import { v, invalid, parseBody, checkField, parseEnv, VALIDATE_RUNTIME } from "../src/index.ts";
|
||||
|
||||
const login = v.object({
|
||||
email: v.string().email(),
|
||||
@@ -33,6 +34,51 @@ test("required (non-optional) empty fields fail", () => {
|
||||
expect(r.errors.password).toBe("Required");
|
||||
});
|
||||
|
||||
test("required(message) customizes empty-field errors and overrides optional", () => {
|
||||
const schema = v.object({
|
||||
email: v.string().required("Enter your email or username"),
|
||||
password: v.string().optional().required("Enter your password"),
|
||||
accepted: v.boolean().required("Accept the terms to continue"),
|
||||
});
|
||||
const result = schema.parse({ accepted: false });
|
||||
|
||||
expect(result.errors).toEqual({
|
||||
email: "Enter your email or username",
|
||||
password: "Enter your password",
|
||||
accepted: "Accept the terms to continue",
|
||||
});
|
||||
expect(schema.describe().fields.email.requiredMessage).toBe("Enter your email or username");
|
||||
});
|
||||
|
||||
test("browser validation displays the serialized custom required message", () => {
|
||||
const schema = v.object({
|
||||
email: v.string().required("Enter your email or username"),
|
||||
});
|
||||
const win = new Window() as unknown as Window & Record<string, unknown>;
|
||||
win.document.body.innerHTML = `
|
||||
<form data-schema="login">
|
||||
<input name="email">
|
||||
<span data-error="email"></span>
|
||||
</form>`;
|
||||
win.__wireSchemas = { login: schema.describe() };
|
||||
(globalThis as Record<string, unknown>).window = win;
|
||||
(globalThis as Record<string, unknown>).document = win.document;
|
||||
|
||||
try {
|
||||
(0, eval)(VALIDATE_RUNTIME);
|
||||
const runtime = win.__wireValidate as { init(root: Document): void };
|
||||
runtime.init(win.document as unknown as Document);
|
||||
win.document.querySelector("input")!.dispatchEvent(new win.Event("blur", { bubbles: true }));
|
||||
|
||||
expect(win.document.querySelector("[data-error=email]")!.textContent).toBe(
|
||||
"Enter your email or username",
|
||||
);
|
||||
} finally {
|
||||
delete (globalThis as Record<string, unknown>).window;
|
||||
delete (globalThis as Record<string, unknown>).document;
|
||||
}
|
||||
});
|
||||
|
||||
test("describe emits a JSON descriptor", () => {
|
||||
const d = login.describe();
|
||||
expect(d.type).toBe("object");
|
||||
@@ -46,6 +92,9 @@ test("checkField coerces and applies rules", () => {
|
||||
);
|
||||
expect(checkField({ type: "number", rules: [{ kind: "min", n: 18 }] }, "20").value).toBe(20);
|
||||
expect(checkField({ type: "string", optional: true, rules: [] }, "").error).toBeNull();
|
||||
expect(
|
||||
checkField({ type: "string", requiredMessage: "Email is required", rules: [] }, "").error,
|
||||
).toBe("Email is required");
|
||||
});
|
||||
|
||||
test("invalid() returns a 400 with errors", async () => {
|
||||
|
||||
Reference in New Issue
Block a user