ctrovalidate-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ctrotech
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # ctrovalidate-core
2
+
3
+ **Zero-dependency, platform-agnostic validation engine for JavaScript and TypeScript.**
4
+
5
+ [![npm version](https://img.shields.io/npm/v/ctrovalidate-core.svg)](https://www.npmjs.com/package/ctrovalidate-core)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ The headless validation engine at the heart of the Ctrovalidate ecosystem. Provides pure validation logic with zero runtime dependencies — runs in Node.js, browsers, edge functions, and any JavaScript environment.
9
+
10
+ ---
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install ctrovalidate-core
16
+ ```
17
+
18
+ ---
19
+
20
+ ## Quick Start
21
+
22
+ ```typescript
23
+ import { validate } from 'ctrovalidate-core';
24
+
25
+ const results = validate(
26
+ { email: 'user@example.com', age: 25 },
27
+ { email: 'required|email', age: 'required|min:18' }
28
+ );
29
+ // { email: { isValid: true, error: null, rule: null }, age: { ... } }
30
+ ```
31
+
32
+ ---
33
+
34
+ ## API Overview
35
+
36
+ | Function | Description |
37
+ |----------|-------------|
38
+ | `validateValue(value, rules, options?)` | Validate a single value synchronously |
39
+ | `validate(data, schema, options?)` | Validate an entire data object synchronously |
40
+ | `validateValueAsync(value, rules, options?)` | Validate a single value asynchronously |
41
+ | `validateAsync(data, schema, options?)` | Validate an entire data object asynchronously |
42
+
43
+ **Full documentation:** [ctrovalidate-core API Reference](https://ctrovalidate.vercel.app/api/core) · [Guide](https://ctrovalidate.vercel.app/guide/core)
44
+
45
+ ---
46
+
47
+ ## Features
48
+
49
+ - **22 built-in rules**: `required`, `email`, `min`, `max`, `minLength`, `maxLength`, `alpha`, `alphaNum`, `alphaDash`, `alphaSpaces`, `numeric`, `integer`, `decimal`, `url`, `ipAddress`, `json`, `phone`, `creditCard`, `strongPassword`, `exactLength`, `between`, `sameAs`
50
+ - **4 validation functions**: Sync and async, single value and full object
51
+ - **Schema formats**: String (`"required|email"`), array, or hybrid
52
+ - **Rule aliases**: Recursive macro expansion with cycle protection
53
+ - **i18n**: Built-in `Translator` with locale switching and parameter interpolation
54
+ - **Async support**: `AbortSignal` integration for race-condition-free remote validation
55
+ - **Zero dependencies**: ~5KB minified
56
+
57
+ ---
58
+
59
+ ## Documentation
60
+
61
+ - [Full API Reference](https://ctrovalidate.vercel.app/api/core)
62
+ - [Core Concepts Guide](https://ctrovalidate.vercel.app/guide/core)
63
+ - [Rules Catalog](https://ctrovalidate.vercel.app/guide/rules)
64
+ - [Schema System](https://ctrovalidate.vercel.app/guide/schemas)
65
+ - [Custom Rules](https://ctrovalidate.vercel.app/advanced/custom-rules)
66
+ - [Async Validation](https://ctrovalidate.vercel.app/advanced/async)
67
+ - [i18n & Localization](https://ctrovalidate.vercel.app/advanced/i18n)
68
+
69
+ ---
70
+
71
+ ## License
72
+
73
+ MIT © [Ctrotech](https://github.com/ctrotech-tutor)
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";var Y=Object.defineProperty;var $=e=>{throw TypeError(e)};var ee=(e,t,r)=>t in e?Y(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r;var p=(e,t,r)=>ee(e,typeof t!="symbol"?t+"":t,r),w=(e,t,r)=>t.has(e)||$("Cannot "+r);var d=(e,t,r)=>(w(e,t,"read from private field"),r?r.call(e):t.get(e)),A=(e,t,r)=>t.has(e)?$("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,r),N=(e,t,r,s)=>(w(e,t,"write to private field"),s?s.call(e,r):t.set(e,r),r);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function x(e){if(!e||typeof e!="string")return[];const t=[],r=e.split("|");for(const s of r){const n=s.trim();if(n==="")continue;const a=n.indexOf(":");if(a===-1)t.push({name:n,params:[]});else{const i=n.substring(0,a).trim(),u=n.substring(a+1).trim();if(i){const f=u.split(",").map(o=>o.trim());t.push({name:i,params:f})}}}return t}function b(e,t={},r=new Set){if(!e)return[];let s;if(typeof e=="string")s=x(e);else if(Array.isArray(e))s=e;else return[];return s.flatMap(n=>{const a=typeof n=="string"?n.split(":")[0]:n.name;if(t[a]&&!r.has(a)){const i=new Set(r);return i.add(a),b(t[a],t,i)}return typeof n=="string"?x(n):[n]})}var F=(e=>(e[e.NONE=0]="NONE",e[e.ERROR=1]="ERROR",e[e.WARN=2]="WARN",e[e.INFO=3]="INFO",e[e.DEBUG=4]="DEBUG",e))(F||{});const g=class g{constructor(t=0){p(this,"level");this.level=t}setLevel(t){this.level=t}static setLevel(t){this.globalLevel=t}format(t){return`${g.prefix} ${t}`}error(t,...r){this.level>=1&&console.error(this.format(t),...r)}warn(t,...r){this.level>=2&&console.warn(this.format(t),...r)}info(t,...r){this.level>=3&&console.info(this.format(t),...r)}debug(t,...r){this.level>=4&&console.debug(this.format(t),...r)}static error(t,...r){this.globalLevel>=1&&console.error(`${this.prefix} ${t}`,...r)}static warn(t,...r){this.globalLevel>=2&&console.warn(`${this.prefix} ${t}`,...r)}static info(t,...r){this.globalLevel>=3&&console.info(`${this.prefix} ${t}`,...r)}static debug(t,...r){this.globalLevel>=4&&console.debug(`${this.prefix} ${t}`,...r)}};p(g,"globalLevel",1),p(g,"prefix","[Ctrovalidate]");let R=g;const P=e=>e==null?!1:typeof e=="boolean"?e:String(e).trim()!=="",te=/^[^\s@]+@[^\s@]+\.[^\s@]+$/,M=e=>e?te.test(String(e)):!0,T=(e,t=[])=>{if(e==null||e==="")return!0;if(!t[0])return console.error("[Ctrovalidate] Missing parameter for 'min' rule."),!1;const r=Number(t[0]);return Number(e)>=r},V=(e,t=[])=>{if(e==null||e==="")return!0;if(!t[0])return console.error("[Ctrovalidate] Missing parameter for 'max' rule."),!1;const r=Number(t[0]);return Number(e)<=r},z=(e,t=[])=>{if(e==null||e==="")return!0;if(!t[0])return console.error("[Ctrovalidate] Missing parameter for 'minLength' rule."),!1;const r=Number(t[0]);return String(e).length>=r},C=(e,t=[])=>{if(e==null||e==="")return!0;if(!t[0])return console.error("[Ctrovalidate] Missing parameter for 'maxLength' rule."),!1;const r=Number(t[0]);return String(e).length<=r},re=/^[a-zA-Z]+$/,O=e=>e?re.test(String(e)):!0,ne=/^[a-zA-Z0-9]+$/,L=e=>e?ne.test(String(e)):!0,se=/^[a-zA-Z0-9-_]+$/,I=e=>e?se.test(String(e)):!0,ae=/^[a-zA-Z\s]+$/,j=e=>e?ae.test(String(e)):!0,Z=e=>!e&&e!==0?!0:!isNaN(Number(e))&&!isNaN(parseFloat(String(e))),ie=/^-?\d+$/,v=e=>!e&&e!==0?!0:ie.test(String(e)),oe=/^-?\d+(\.\d+)?$/,D=e=>!e&&e!==0?!0:oe.test(String(e)),le=/^(https?:\/\/)([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/,E=e=>e?le.test(String(e)):!0,de=/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,ce=/^(?:(?:[a-fA-F\d]{1,4}:){7}(?:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,2}|:)|(?:[a-fA-F\d]{1,4}:){4}(?:(?::[a-fA-F\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,3}|:)|(?:[a-fA-F\d]{1,4}:){3}(?:(?::[a-fA-F\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,4}|:)|(?:[a-fA-F\d]{1,4}:){2}(?:(?::[a-fA-F\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,5}|:)|(?:[a-fA-F\d]{1,4}:){1}(?:(?::[a-fA-F\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-F\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?$/,q=e=>{if(!e)return!0;const t=String(e);return de.test(t)||ce.test(t)},U=e=>{if(!e)return!0;try{const t=JSON.parse(String(e));return typeof t=="object"&&t!==null}catch{return!1}},ue=/^[+]?([(]?[0-9]{1,4}[)]?)[-\s.]?[0-9]{3,4}[-\s.]?[0-9]{4,6}$/,k=e=>e?ue.test(String(e)):!0,B=e=>{if(!e)return!0;const t=String(e).replace(/[- ]/g,"");if(!/^\d+$/.test(t))return!1;let r=0;const s=t.length%2;for(let n=0;n<t.length;n++){let a=parseInt(t[n],10);n%2===s&&(a*=2,a>9&&(a-=9)),r+=a}return r%10===0},fe=/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/,G=e=>e?fe.test(String(e)):!0,J=(e,t=[])=>{if(e==null||e==="")return!0;if(!t[0])return console.error("[Ctrovalidate] Missing parameter for 'exactLength' rule."),!1;const r=Number(t[0]);return String(e).length===r},W=(e,t=[])=>{if(e==null||e==="")return!0;if(t.length<2)return console.error("[Ctrovalidate] Missing parameters for 'between' rule."),!1;const r=Number(t[0]),s=Number(t[1]);if(isNaN(r)||isNaN(s))return!1;if(!isNaN(Number(e))&&typeof e!="string"){const i=Number(e);return i>=r&&i<=s}const n=String(e);if(!isNaN(Number(n))&&n!==""){const i=Number(n);return i>=r&&i<=s}const a=n.length;return a>=r&&a<=s},_=(e,t=[])=>{if(t.length===0||t[0]===void 0)return console.error("[Ctrovalidate] Missing parameter for 'sameAs' rule."),!1;const r=t[0];return e===r},S=Object.freeze(Object.defineProperty({__proto__:null,alpha:O,alphaDash:I,alphaNum:L,alphaSpaces:j,between:W,creditCard:B,decimal:D,email:M,exactLength:J,integer:v,ipAddress:q,json:U,max:V,maxLength:C,min:T,minLength:z,numeric:Z,phone:k,required:P,sameAs:_,strongPassword:G,url:E},Symbol.toStringTag,{value:"Module"})),me={required:"This field is required.",email:"Please enter a valid email address.",min:"Minimum value is {0}.",max:"Maximum value is {0}.",minLength:"Minimum length is {0} characters.",maxLength:"Maximum length is {0} characters.",alpha:"This field may only contain alphabetic characters.",alphaNum:"This field may only contain alpha-numeric characters.",alphaDash:"This field may only contain alpha-numeric characters as well as dashes and underscores.",alphaSpaces:"This field may only contain alphabetic characters and spaces.",numeric:"This field must be a number.",integer:"This field must be an integer.",decimal:"This field must be a decimal number.",url:"Please enter a valid URL.",ipAddress:"Please enter a valid IP address.",json:"Please enter a valid JSON string.",phone:"Please enter a valid phone number.",creditCard:"Please enter a valid credit card number.",strongPassword:"Password must be at least 8 characters long and include an uppercase letter, a lowercase letter, a number, and a symbol.",exactLength:"This field must be exactly {0} characters long.",between:"This field must be between {0} and {1}.",sameAs:"This field must match {0}."};var c,m;class H{constructor(){A(this,c,{en:me});A(this,m,"en")}setLocale(t){if(!d(this,c)[t]){console.warn(`[Ctrovalidate] Locale "${t}" not found. Falling back to "en".`),N(this,m,"en");return}N(this,m,t)}addMessages(t,r){d(this,c)[t]={...d(this,c)[t]||{},...r}}translate(t,r=[],s){const n=s||d(this,m),a=d(this,c)[n]||d(this,c).en;return(a[t]||a.default||"Invalid input.").replace(/{(\d+)}/g,(u,f)=>{const o=parseInt(f,10);return r[o]!==void 0?String(r[o]):u})}get currentLocale(){return d(this,m)}}c=new WeakMap,m=new WeakMap;const y=new H;function K(e,t,r={}){const{customRules:s={},aliases:n={},messages:a={},locale:i}=r,u=b(t,n),f={...S,...s};for(const o of u){const l=f[o.name];if(!l)continue;const h=l(e,o.params);if(!(h instanceof Promise)&&!h)return{isValid:!1,error:a[o.name]||a["*"]||y.translate(o.name,o.params,i),rule:o.name}}return{isValid:!0,error:null,rule:null}}function he(e,t,r={}){const s={};for(const n in t)s[n]=K(e[n],t[n],r);return s}async function Q(e,t,r={}){const{customRules:s={},aliases:n={},messages:a={},locale:i,signal:u}=r,f=b(t,n),o={...S,...s};for(const l of f){const h=o[l.name];if(!h)continue;if(!await h(e,l.params,null,u))return{isValid:!1,error:a[l.name]||a["*"]||y.translate(l.name,l.params,i),rule:l.name}}return{isValid:!0,error:null,rule:null}}async function ge(e,t,r={}){const s={};for(const n in t)s[n]=await Q(e[n],t[n],r);return s}const pe="1.0.0";exports.LogLevel=F;exports.Logger=R;exports.Translator=H;exports.alpha=O;exports.alphaDash=I;exports.alphaNum=L;exports.alphaSpaces=j;exports.between=W;exports.creditCard=B;exports.decimal=D;exports.email=M;exports.exactLength=J;exports.integer=v;exports.ipAddress=q;exports.json=U;exports.max=V;exports.maxLength=C;exports.min=T;exports.minLength=z;exports.normalizeRules=b;exports.numeric=Z;exports.parseRules=x;exports.phone=k;exports.required=P;exports.rules=S;exports.sameAs=_;exports.strongPassword=G;exports.translator=y;exports.url=E;exports.validate=he;exports.validateAsync=ge;exports.validateValue=K;exports.validateValueAsync=Q;exports.version=pe;
@@ -0,0 +1,295 @@
1
+ /**
2
+ * Checks if a value contains only alphabetic characters.
3
+ */
4
+ export declare const alpha: RuleLogic;
5
+
6
+ /**
7
+ * Checks if a value contains only alphanumeric characters, dashes, and underscores.
8
+ */
9
+ export declare const alphaDash: RuleLogic;
10
+
11
+ /**
12
+ * Checks if a value contains only alphanumeric characters.
13
+ */
14
+ export declare const alphaNum: RuleLogic;
15
+
16
+ /**
17
+ * Checks if a value contains only alphabetic characters and spaces.
18
+ */
19
+ export declare const alphaSpaces: RuleLogic;
20
+
21
+ /**
22
+ * The logic function for an asynchronous validation rule.
23
+ * @template Context The type of the context object.
24
+ */
25
+ export declare type AsyncRuleLogic<Context = unknown> = (value: unknown, params?: unknown[], context?: Context | null, signal?: AbortSignal) => Promise<boolean>;
26
+
27
+ /**
28
+ * Checks if a value is between a min and max (inclusive).
29
+ * Supports numbers and string lengths.
30
+ */
31
+ export declare const between: RuleLogic;
32
+
33
+ /**
34
+ * Checks if a value is a valid credit card number using the Luhn algorithm.
35
+ */
36
+ export declare const creditCard: RuleLogic;
37
+
38
+ /**
39
+ * Checks if a value is a valid decimal number.
40
+ */
41
+ export declare const decimal: RuleLogic;
42
+
43
+ /**
44
+ * Information about a dependency between fields.
45
+ */
46
+ export declare interface DependencyDefinition {
47
+ controllerName: string;
48
+ type: 'checked' | 'value' | 'present' | string;
49
+ value?: unknown;
50
+ }
51
+
52
+ /**
53
+ * Checks if a value is a valid email address.
54
+ */
55
+ export declare const email: RuleLogic;
56
+
57
+ /**
58
+ * Checks if a value has an exact length.
59
+ */
60
+ export declare const exactLength: RuleLogic;
61
+
62
+ /**
63
+ * Checks if a value is an integer.
64
+ */
65
+ export declare const integer: RuleLogic;
66
+
67
+ /**
68
+ * Checks if a value is a valid IP address (v4 or v6).
69
+ */
70
+ export declare const ipAddress: RuleLogic;
71
+
72
+ /**
73
+ * Checks if a value is valid JSON.
74
+ */
75
+ export declare const json: RuleLogic;
76
+
77
+ export declare type LocaleDictionary = Record<string, string>;
78
+
79
+ export declare class Logger {
80
+ private level;
81
+ private static globalLevel;
82
+ private static prefix;
83
+ constructor(level?: LogLevel);
84
+ setLevel(level: LogLevel): void;
85
+ static setLevel(level: LogLevel): void;
86
+ private format;
87
+ error(message: string, ...args: unknown[]): void;
88
+ warn(message: string, ...args: unknown[]): void;
89
+ info(message: string, ...args: unknown[]): void;
90
+ debug(message: string, ...args: unknown[]): void;
91
+ static error(message: string, ...args: unknown[]): void;
92
+ static warn(message: string, ...args: unknown[]): void;
93
+ static info(message: string, ...args: unknown[]): void;
94
+ static debug(message: string, ...args: unknown[]): void;
95
+ }
96
+
97
+ export declare enum LogLevel {
98
+ NONE = 0,
99
+ ERROR = 1,
100
+ WARN = 2,
101
+ INFO = 3,
102
+ DEBUG = 4
103
+ }
104
+
105
+ /**
106
+ * Checks if a value meets a maximum numeric requirement.
107
+ */
108
+ export declare const max: RuleLogic;
109
+
110
+ /**
111
+ * Checks if a value does not exceed a maximum length requirement.
112
+ */
113
+ export declare const maxLength: RuleLogic;
114
+
115
+ /**
116
+ * Checks if a value meets a minimum numeric requirement.
117
+ */
118
+ export declare const min: RuleLogic;
119
+
120
+ /**
121
+ * Checks if a value meets a minimum length requirement.
122
+ */
123
+ export declare const minLength: RuleLogic;
124
+
125
+ /**
126
+ * Normalizes a SchemaRule (string, string[], or RuleDefinition[]) into a uniform RuleDefinition[].
127
+ * This provides high flexibility for developers to define rules as they prefer.
128
+ * Supports recursive expansion of aliases.
129
+ *
130
+ * @param rules - The rules to normalize.
131
+ * @param aliases - Optional dictionary of rule aliases.
132
+ * @param seen - Used internally to prevent infinite recursion.
133
+ * @returns An array of normalized rule definitions.
134
+ */
135
+ export declare function normalizeRules(rules: SchemaRule, aliases?: Record<string, SchemaRule>, seen?: Set<string>): RuleDefinition[];
136
+
137
+ /**
138
+ * Checks if a value is numeric.
139
+ */
140
+ export declare const numeric: RuleLogic;
141
+
142
+ /**
143
+ * Parses a rule string (e.g., "required|minLength:3|between:1,10") into a structured array.
144
+ *
145
+ * @param rulesString - The string from the data-ctrovalidate-rules attribute.
146
+ * @returns An array of rule objects.
147
+ */
148
+ export declare function parseRules(rulesString: string | null | undefined): RuleDefinition[];
149
+
150
+ /**
151
+ * Checks if a value is a valid phone number.
152
+ */
153
+ export declare const phone: RuleLogic;
154
+
155
+ /**
156
+ * Checks if a value is present.
157
+ */
158
+ export declare const required: RuleLogic;
159
+
160
+ /**
161
+ * A parsed rule definition.
162
+ */
163
+ export declare interface RuleDefinition {
164
+ name: string;
165
+ params: unknown[];
166
+ }
167
+
168
+ /**
169
+ * @file Core type definitions for Ctrovalidate.
170
+ */
171
+ /**
172
+ * The logic function for a synchronous validation rule.
173
+ * @template Context The type of the context object (e.g., generic context or platform specific element).
174
+ */
175
+ export declare type RuleLogic<Context = unknown> = (value: unknown, params?: unknown[], context?: Context | null) => boolean;
176
+
177
+ export declare namespace rules {
178
+ export {
179
+ required,
180
+ email,
181
+ min,
182
+ max,
183
+ minLength,
184
+ maxLength,
185
+ alpha,
186
+ alphaNum,
187
+ alphaDash,
188
+ alphaSpaces,
189
+ numeric,
190
+ integer,
191
+ decimal,
192
+ url,
193
+ ipAddress,
194
+ json,
195
+ phone,
196
+ creditCard,
197
+ strongPassword,
198
+ exactLength,
199
+ between,
200
+ sameAs
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Checks if a value is the same as a target value.
206
+ */
207
+ export declare const sameAs: RuleLogic;
208
+
209
+ /**
210
+ * A rule definition in a schema, supporting strings, array of strings,
211
+ * RuleDefinitions, or a hybrid array of both strings and definitions.
212
+ */
213
+ export declare type SchemaRule = string | (string | RuleDefinition)[];
214
+
215
+ /**
216
+ * Checks if a value is a "strong" password.
217
+ * (At least 8 chars, 1 uppercase, 1 lowercase, 1 number, 1 special char)
218
+ */
219
+ export declare const strongPassword: RuleLogic;
220
+
221
+ /**
222
+ * Handles internationalization and message interpolation for Ctrovalidate.
223
+ */
224
+ export declare class Translator {
225
+ #private;
226
+ /**
227
+ * Set the current locale.
228
+ */
229
+ setLocale(locale: string): void;
230
+ /**
231
+ * Add or update messages for a specific locale.
232
+ */
233
+ addMessages(locale: string, messages: LocaleDictionary): void;
234
+ /**
235
+ * Translates a rule name with optional parameters.
236
+ */
237
+ translate(rule: string, params?: unknown[], localeOverride?: string): string;
238
+ /**
239
+ * Get the current active locale.
240
+ */
241
+ get currentLocale(): string;
242
+ }
243
+
244
+ export declare const translator: Translator;
245
+
246
+ /**
247
+ * Checks if a value is a valid URL.
248
+ */
249
+ export declare const url: RuleLogic;
250
+
251
+ /**
252
+ * Validates an entire data object against a schema.
253
+ */
254
+ export declare function validate<T extends object>(data: T, schema: Record<string, SchemaRule>, options?: ValidationOptions): Record<string, ValidationResult>;
255
+
256
+ /**
257
+ * Validates an entire data object asynchronously against a schema.
258
+ */
259
+ export declare function validateAsync<T extends object>(data: T, schema: Record<string, SchemaRule>, options?: ValidationOptions): Promise<Record<string, ValidationResult>>;
260
+
261
+ /**
262
+ * Validates a single value against a set of rules.
263
+ */
264
+ export declare function validateValue(value: unknown, rules: SchemaRule, options?: ValidationOptions): ValidationResult;
265
+
266
+ /**
267
+ * Validates a single value asynchronously against a set of rules.
268
+ */
269
+ export declare function validateValueAsync(value: unknown, rules: SchemaRule, options?: ValidationOptions): Promise<ValidationResult>;
270
+
271
+ export declare interface ValidationOptions {
272
+ customRules?: Record<string, RuleLogic | AsyncRuleLogic>;
273
+ aliases?: Record<string, SchemaRule>;
274
+ messages?: Record<string, string>;
275
+ locale?: string;
276
+ signal?: AbortSignal;
277
+ }
278
+
279
+ /**
280
+ * Result of a validation execution.
281
+ */
282
+ export declare interface ValidationResult {
283
+ isValid: boolean;
284
+ error: string | null;
285
+ rule: string | null;
286
+ }
287
+
288
+ /**
289
+ * A validation schema object mapping field names to rules.
290
+ */
291
+ export declare type ValidationSchema = Record<string, SchemaRule>;
292
+
293
+ export declare const version = "1.0.0";
294
+
295
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,368 @@
1
+ var P = Object.defineProperty;
2
+ var x = (e) => {
3
+ throw TypeError(e);
4
+ };
5
+ var M = (e, t, r) => t in e ? P(e, t, { enumerable: !0, configurable: !0, writable: !0, value: r }) : e[t] = r;
6
+ var p = (e, t, r) => M(e, typeof t != "symbol" ? t + "" : t, r), R = (e, t, r) => t.has(e) || x("Cannot " + r);
7
+ var d = (e, t, r) => (R(e, t, "read from private field"), r ? r.call(e) : t.get(e)), b = (e, t, r) => t.has(e) ? x("Cannot add the same private member more than once") : t instanceof WeakSet ? t.add(e) : t.set(e, r), N = (e, t, r, s) => (R(e, t, "write to private field"), s ? s.call(e, r) : t.set(e, r), r);
8
+ function S(e) {
9
+ if (!e || typeof e != "string")
10
+ return [];
11
+ const t = [], r = e.split("|");
12
+ for (const s of r) {
13
+ const n = s.trim();
14
+ if (n === "")
15
+ continue;
16
+ const i = n.indexOf(":");
17
+ if (i === -1)
18
+ t.push({ name: n, params: [] });
19
+ else {
20
+ const a = n.substring(0, i).trim(), u = n.substring(i + 1).trim();
21
+ if (a) {
22
+ const f = u.split(",").map((o) => o.trim());
23
+ t.push({ name: a, params: f });
24
+ }
25
+ }
26
+ }
27
+ return t;
28
+ }
29
+ function A(e, t = {}, r = /* @__PURE__ */ new Set()) {
30
+ if (!e) return [];
31
+ let s;
32
+ if (typeof e == "string")
33
+ s = S(e);
34
+ else if (Array.isArray(e))
35
+ s = e;
36
+ else
37
+ return [];
38
+ return s.flatMap((n) => {
39
+ const i = typeof n == "string" ? n.split(":")[0] : n.name;
40
+ if (t[i] && !r.has(i)) {
41
+ const a = new Set(r);
42
+ return a.add(i), A(t[i], t, a);
43
+ }
44
+ return typeof n == "string" ? S(n) : [n];
45
+ });
46
+ }
47
+ var T = /* @__PURE__ */ ((e) => (e[e.NONE = 0] = "NONE", e[e.ERROR = 1] = "ERROR", e[e.WARN = 2] = "WARN", e[e.INFO = 3] = "INFO", e[e.DEBUG = 4] = "DEBUG", e))(T || {});
48
+ const g = class g {
49
+ constructor(t = 0) {
50
+ p(this, "level");
51
+ this.level = t;
52
+ }
53
+ setLevel(t) {
54
+ this.level = t;
55
+ }
56
+ static setLevel(t) {
57
+ this.globalLevel = t;
58
+ }
59
+ format(t) {
60
+ return `${g.prefix} ${t}`;
61
+ }
62
+ // Instance methods
63
+ error(t, ...r) {
64
+ this.level >= 1 && console.error(this.format(t), ...r);
65
+ }
66
+ warn(t, ...r) {
67
+ this.level >= 2 && console.warn(this.format(t), ...r);
68
+ }
69
+ info(t, ...r) {
70
+ this.level >= 3 && console.info(this.format(t), ...r);
71
+ }
72
+ debug(t, ...r) {
73
+ this.level >= 4 && console.debug(this.format(t), ...r);
74
+ }
75
+ // Static methods (using global level)
76
+ static error(t, ...r) {
77
+ this.globalLevel >= 1 && console.error(`${this.prefix} ${t}`, ...r);
78
+ }
79
+ static warn(t, ...r) {
80
+ this.globalLevel >= 2 && console.warn(`${this.prefix} ${t}`, ...r);
81
+ }
82
+ static info(t, ...r) {
83
+ this.globalLevel >= 3 && console.info(`${this.prefix} ${t}`, ...r);
84
+ }
85
+ static debug(t, ...r) {
86
+ this.globalLevel >= 4 && console.debug(`${this.prefix} ${t}`, ...r);
87
+ }
88
+ };
89
+ p(g, "globalLevel", 1), p(g, "prefix", "[Ctrovalidate]");
90
+ let y = g;
91
+ const z = (e) => e == null ? !1 : typeof e == "boolean" ? e : String(e).trim() !== "", V = /^[^\s@]+@[^\s@]+\.[^\s@]+$/, C = (e) => e ? V.test(String(e)) : !0, O = (e, t = []) => {
92
+ if (e == null || e === "") return !0;
93
+ if (!t[0])
94
+ return console.error("[Ctrovalidate] Missing parameter for 'min' rule."), !1;
95
+ const r = Number(t[0]);
96
+ return Number(e) >= r;
97
+ }, I = (e, t = []) => {
98
+ if (e == null || e === "") return !0;
99
+ if (!t[0])
100
+ return console.error("[Ctrovalidate] Missing parameter for 'max' rule."), !1;
101
+ const r = Number(t[0]);
102
+ return Number(e) <= r;
103
+ }, Z = (e, t = []) => {
104
+ if (e == null || e === "") return !0;
105
+ if (!t[0])
106
+ return console.error("[Ctrovalidate] Missing parameter for 'minLength' rule."), !1;
107
+ const r = Number(t[0]);
108
+ return String(e).length >= r;
109
+ }, E = (e, t = []) => {
110
+ if (e == null || e === "") return !0;
111
+ if (!t[0])
112
+ return console.error("[Ctrovalidate] Missing parameter for 'maxLength' rule."), !1;
113
+ const r = Number(t[0]);
114
+ return String(e).length <= r;
115
+ }, j = /^[a-zA-Z]+$/, D = (e) => e ? j.test(String(e)) : !0, L = /^[a-zA-Z0-9]+$/, q = (e) => e ? L.test(String(e)) : !0, U = /^[a-zA-Z0-9-_]+$/, k = (e) => e ? U.test(String(e)) : !0, B = /^[a-zA-Z\s]+$/, G = (e) => e ? B.test(String(e)) : !0, J = (e) => !e && e !== 0 ? !0 : !isNaN(Number(e)) && !isNaN(parseFloat(String(e))), W = /^-?\d+$/, _ = (e) => !e && e !== 0 ? !0 : W.test(String(e)), H = /^-?\d+(\.\d+)?$/, K = (e) => !e && e !== 0 ? !0 : H.test(String(e)), Q = /^(https?:\/\/)([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/, X = (e) => e ? Q.test(String(e)) : !0, Y = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/, v = /^(?:(?:[a-fA-F\d]{1,4}:){7}(?:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,2}|:)|(?:[a-fA-F\d]{1,4}:){4}(?:(?::[a-fA-F\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,3}|:)|(?:[a-fA-F\d]{1,4}:){3}(?:(?::[a-fA-F\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,4}|:)|(?:[a-fA-F\d]{1,4}:){2}(?:(?::[a-fA-F\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,5}|:)|(?:[a-fA-F\d]{1,4}:){1}(?:(?::[a-fA-F\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-F\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?$/, ee = (e) => {
116
+ if (!e) return !0;
117
+ const t = String(e);
118
+ return Y.test(t) || v.test(t);
119
+ }, te = (e) => {
120
+ if (!e) return !0;
121
+ try {
122
+ const t = JSON.parse(String(e));
123
+ return typeof t == "object" && t !== null;
124
+ } catch {
125
+ return !1;
126
+ }
127
+ }, re = /^[+]?([(]?[0-9]{1,4}[)]?)[-\s.]?[0-9]{3,4}[-\s.]?[0-9]{4,6}$/, ne = (e) => e ? re.test(String(e)) : !0, se = (e) => {
128
+ if (!e) return !0;
129
+ const t = String(e).replace(/[- ]/g, "");
130
+ if (!/^\d+$/.test(t)) return !1;
131
+ let r = 0;
132
+ const s = t.length % 2;
133
+ for (let n = 0; n < t.length; n++) {
134
+ let i = parseInt(t[n], 10);
135
+ n % 2 === s && (i *= 2, i > 9 && (i -= 9)), r += i;
136
+ }
137
+ return r % 10 === 0;
138
+ }, ie = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/, ae = (e) => e ? ie.test(String(e)) : !0, oe = (e, t = []) => {
139
+ if (e == null || e === "") return !0;
140
+ if (!t[0])
141
+ return console.error("[Ctrovalidate] Missing parameter for 'exactLength' rule."), !1;
142
+ const r = Number(t[0]);
143
+ return String(e).length === r;
144
+ }, le = (e, t = []) => {
145
+ if (e == null || e === "") return !0;
146
+ if (t.length < 2)
147
+ return console.error("[Ctrovalidate] Missing parameters for 'between' rule."), !1;
148
+ const r = Number(t[0]), s = Number(t[1]);
149
+ if (isNaN(r) || isNaN(s))
150
+ return !1;
151
+ if (!isNaN(Number(e)) && typeof e != "string") {
152
+ const a = Number(e);
153
+ return a >= r && a <= s;
154
+ }
155
+ const n = String(e);
156
+ if (!isNaN(Number(n)) && n !== "") {
157
+ const a = Number(n);
158
+ return a >= r && a <= s;
159
+ }
160
+ const i = n.length;
161
+ return i >= r && i <= s;
162
+ }, de = (e, t = []) => {
163
+ if (t.length === 0 || t[0] === void 0)
164
+ return console.error("[Ctrovalidate] Missing parameter for 'sameAs' rule."), !1;
165
+ const r = t[0];
166
+ return e === r;
167
+ }, $ = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
168
+ __proto__: null,
169
+ alpha: D,
170
+ alphaDash: k,
171
+ alphaNum: q,
172
+ alphaSpaces: G,
173
+ between: le,
174
+ creditCard: se,
175
+ decimal: K,
176
+ email: C,
177
+ exactLength: oe,
178
+ integer: _,
179
+ ipAddress: ee,
180
+ json: te,
181
+ max: I,
182
+ maxLength: E,
183
+ min: O,
184
+ minLength: Z,
185
+ numeric: J,
186
+ phone: ne,
187
+ required: z,
188
+ sameAs: de,
189
+ strongPassword: ae,
190
+ url: X
191
+ }, Symbol.toStringTag, { value: "Module" })), ce = {
192
+ required: "This field is required.",
193
+ email: "Please enter a valid email address.",
194
+ min: "Minimum value is {0}.",
195
+ max: "Maximum value is {0}.",
196
+ minLength: "Minimum length is {0} characters.",
197
+ maxLength: "Maximum length is {0} characters.",
198
+ alpha: "This field may only contain alphabetic characters.",
199
+ alphaNum: "This field may only contain alpha-numeric characters.",
200
+ alphaDash: "This field may only contain alpha-numeric characters as well as dashes and underscores.",
201
+ alphaSpaces: "This field may only contain alphabetic characters and spaces.",
202
+ numeric: "This field must be a number.",
203
+ integer: "This field must be an integer.",
204
+ decimal: "This field must be a decimal number.",
205
+ url: "Please enter a valid URL.",
206
+ ipAddress: "Please enter a valid IP address.",
207
+ json: "Please enter a valid JSON string.",
208
+ phone: "Please enter a valid phone number.",
209
+ creditCard: "Please enter a valid credit card number.",
210
+ strongPassword: "Password must be at least 8 characters long and include an uppercase letter, a lowercase letter, a number, and a symbol.",
211
+ exactLength: "This field must be exactly {0} characters long.",
212
+ between: "This field must be between {0} and {1}.",
213
+ sameAs: "This field must match {0}."
214
+ };
215
+ var c, m;
216
+ class ue {
217
+ constructor() {
218
+ b(this, c, { en: ce });
219
+ b(this, m, "en");
220
+ }
221
+ /**
222
+ * Set the current locale.
223
+ */
224
+ setLocale(t) {
225
+ if (!d(this, c)[t]) {
226
+ console.warn(
227
+ `[Ctrovalidate] Locale "${t}" not found. Falling back to "en".`
228
+ ), N(this, m, "en");
229
+ return;
230
+ }
231
+ N(this, m, t);
232
+ }
233
+ /**
234
+ * Add or update messages for a specific locale.
235
+ */
236
+ addMessages(t, r) {
237
+ d(this, c)[t] = {
238
+ ...d(this, c)[t] || {},
239
+ ...r
240
+ };
241
+ }
242
+ /**
243
+ * Translates a rule name with optional parameters.
244
+ */
245
+ translate(t, r = [], s) {
246
+ const n = s || d(this, m), i = d(this, c)[n] || d(this, c).en;
247
+ return (i[t] || i.default || "Invalid input.").replace(/{(\d+)}/g, (u, f) => {
248
+ const o = parseInt(f, 10);
249
+ return r[o] !== void 0 ? String(r[o]) : u;
250
+ });
251
+ }
252
+ /**
253
+ * Get the current active locale.
254
+ */
255
+ get currentLocale() {
256
+ return d(this, m);
257
+ }
258
+ }
259
+ c = new WeakMap(), m = new WeakMap();
260
+ const F = new ue();
261
+ function fe(e, t, r = {}) {
262
+ const { customRules: s = {}, aliases: n = {}, messages: i = {}, locale: a } = r, u = A(t, n), f = {
263
+ ...$,
264
+ ...s
265
+ };
266
+ for (const o of u) {
267
+ const l = f[o.name];
268
+ if (!l)
269
+ continue;
270
+ const h = l(e, o.params);
271
+ if (!(h instanceof Promise) && !h)
272
+ return {
273
+ isValid: !1,
274
+ error: i[o.name] || i["*"] || F.translate(o.name, o.params, a),
275
+ rule: o.name
276
+ };
277
+ }
278
+ return {
279
+ isValid: !0,
280
+ error: null,
281
+ rule: null
282
+ };
283
+ }
284
+ function pe(e, t, r = {}) {
285
+ const s = {};
286
+ for (const n in t)
287
+ s[n] = fe(
288
+ e[n],
289
+ t[n],
290
+ r
291
+ );
292
+ return s;
293
+ }
294
+ async function me(e, t, r = {}) {
295
+ const {
296
+ customRules: s = {},
297
+ aliases: n = {},
298
+ messages: i = {},
299
+ locale: a,
300
+ signal: u
301
+ } = r, f = A(t, n), o = {
302
+ ...$,
303
+ ...s
304
+ };
305
+ for (const l of f) {
306
+ const h = o[l.name];
307
+ if (!h)
308
+ continue;
309
+ if (!await h(e, l.params, null, u))
310
+ return {
311
+ isValid: !1,
312
+ error: i[l.name] || i["*"] || F.translate(l.name, l.params, a),
313
+ rule: l.name
314
+ };
315
+ }
316
+ return {
317
+ isValid: !0,
318
+ error: null,
319
+ rule: null
320
+ };
321
+ }
322
+ async function be(e, t, r = {}) {
323
+ const s = {};
324
+ for (const n in t)
325
+ s[n] = await me(
326
+ e[n],
327
+ t[n],
328
+ r
329
+ );
330
+ return s;
331
+ }
332
+ const Ne = "1.0.0";
333
+ export {
334
+ T as LogLevel,
335
+ y as Logger,
336
+ ue as Translator,
337
+ D as alpha,
338
+ k as alphaDash,
339
+ q as alphaNum,
340
+ G as alphaSpaces,
341
+ le as between,
342
+ se as creditCard,
343
+ K as decimal,
344
+ C as email,
345
+ oe as exactLength,
346
+ _ as integer,
347
+ ee as ipAddress,
348
+ te as json,
349
+ I as max,
350
+ E as maxLength,
351
+ O as min,
352
+ Z as minLength,
353
+ A as normalizeRules,
354
+ J as numeric,
355
+ S as parseRules,
356
+ ne as phone,
357
+ z as required,
358
+ $ as rules,
359
+ de as sameAs,
360
+ ae as strongPassword,
361
+ F as translator,
362
+ X as url,
363
+ pe as validate,
364
+ be as validateAsync,
365
+ fe as validateValue,
366
+ me as validateValueAsync,
367
+ Ne as version
368
+ };
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "ctrovalidate-core",
3
+ "version": "1.0.0",
4
+ "description": "Core validation logic for Ctrovalidate (Headless).",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "vite build",
21
+ "test": "vitest run",
22
+ "test:coverage": "vitest run --coverage",
23
+ "lint": "eslint .",
24
+ "format": "prettier . --check"
25
+ },
26
+ "devDependencies": {
27
+ "vite": "^6.0.0",
28
+ "vite-plugin-dts": "^4.0.0",
29
+ "vitest": "^4.0.18",
30
+ "eslint": "^9.39.2",
31
+ "@eslint/js": "^9.39.2",
32
+ "typescript": "^5.9.3",
33
+ "typescript-eslint": "^8.54.0",
34
+ "prettier": "^3.1.1",
35
+ "eslint-config-prettier": "^9.1.0",
36
+ "globals": "^17.2.0"
37
+ },
38
+ "homepage": "https://ctrovalidate.vercel.app/api/core",
39
+ "keywords": ["ctrovalidate", "validation", "form-validation", "javascript", "typescript"],
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/ctrotech-tutor/ctrovalidate-core.git"
43
+ },
44
+ "bugs": {
45
+ "url": "https://github.com/ctrotech-tutor/ctrovalidate-core/issues"
46
+ },
47
+ "publishConfig": { "access": "public" },
48
+ "author": "Ctrotech (https://github.com/ctrotech-tutor)",
49
+ "license": "MIT"
50
+ }