@esmj/schema 0.4.0 → 0.5.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.
@@ -0,0 +1 @@
1
+ 'use strict';var P={abortEarly:true};function S(e,o){if(!o)return e;let i=e?.cause?.key?`${o}.${e.cause.key}`:`${o}`;return {message:`Error parsing key "${i}": ${e.message}`,cause:{key:i}}}function g(e,o,i){!e.errors||e.errors.length===0||e.errors.forEach(u=>{let c=S(u,i);o.push(c);});}function d(e){return {...P,...e}}var N=e=>typeof e=="string"||e instanceof String,k=e=>typeof e=="number"||e instanceof Number,w=e=>e===true||e===false,E=e=>e instanceof Date&&!Number.isNaN(e.getTime()),_=e=>Array.isArray(e),O=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),A={object(e,o){let i=I(O,{...o,type:"object"});return i._getDescription=()=>`object({ ${Object.entries(e).map(([c,r])=>`${c}: ${r._getDescription()}`).join(", ")} })`,h(i,"_parse",(u,c,r)=>{let t=u(c,r),{abortEarly:n}=d(r);if(t.success===false)return t;let s={},a=[];for(let p in e){let f=e[p]._parse(t.data[p],r);if(f.success)s[p]=f.data;else {if(f=f,n!==false){let m=S(f.error,p);return {success:false,error:m,errors:[m]}}g(f,a,p);}}return a.length>0?{success:false,error:a[0],errors:a}:{success:true,data:s}}),i},string(e){return I(N,{...e,type:"string"})},number(e){return I(k,{...e,type:"number"})},boolean(e){return I(w,{...e,type:"boolean"})},date(e){return I(E,{...e,type:"date"})},enum(e,o){let i=t=>e.includes(t),u=t=>`Invalid ${c} value. Expected ${e.map(n=>`"${n}"`).join(" | ")}, received "${t}".`,c="enum",r=I(i,{message:u,...o,type:c});return r._getDescription=()=>`enum(${e.map(t=>`"${t}"`).join(" | ")})`,r},array(e,o){let i=I(_,{...o,type:"array"});return i._getDescription=()=>`array(${e._getDescription()})`,h(i,"_parse",(u,c,r)=>{let t=u(c,r),{abortEarly:n}=d(r);if(t.success===false)return t;let s=[],a=[];for(let p=0;p<t.data.length;p++){let f=e._parse(t.data[p],r);if(f.success)s.push(f.data);else {if(f=f,n!==false){let m=S(f.error,p);return {success:false,error:m,errors:[m]}}g(f,a,p);}}return a.length>0?{success:false,error:a[0],errors:a}:{success:true,data:s}}),i},any(){return I(()=>true)},preprocess(e,o){return h(o,"_parse",(i,u)=>(u=e(u),i(u))),o},union(e,o){return I(r=>{for(let t=0;t<e.length;t++){let n=e[t]._parse(r);if(n.success)return n}return false},{message:r=>`Invalid union value. Expected the value to match one of the schemas:${e.map((t,n)=>` ${n+1}. ${t._getDescription()}`).join(",")} but received "${typeof r}" with value: ${O(r)?JSON.stringify(r):`"${r}"`}`,...o,type:"union"})}};function T(e){return o=>`The value "${o}" must be type of ${e} but is type of "${typeof o}".`}function h(e,o,i){let u=e[o];e[o]=(...c)=>i(u,...c);}function I(e,{type:o="any",name:i,message:u}={}){u=u||T(o);let c={name:i,message:u,type:o},r={_getName(){return i},_getType(){return o},_getDescription(){return this._getName()??this._getType()},_parse(t,n){let s=e(t);if(s===true)return {success:true,data:t};if(typeof s=="object"&&s?.success===true)return s;let a={message:typeof u=="function"?u(t):u};return {success:false,error:a,errors:[a]}},parse(t,n){let s=r._parse(t,n);if(!s.success)throw s=s,new Error(s.error.message,{cause:s.error.cause});return s.data},safeParse(t,n){return r._parse(t,n)},transform(t){return h(this,"_parse",(n,s,a)=>{let p=n(s,a);return p.success&&(p.data=t(p.data)),p}),this},optional(){return h(this,"_parse",(t,n,s)=>{let a=t(n,s);return !a.success&&n===void 0&&(a.data=void 0,a.success=true),a}),this},nullable(){return h(this,"_parse",(t,n,s)=>{let a=t(n,s);return !a.success&&n===null&&(a.data=null,a.success=true),a}),this},nullish(){return h(this,"_parse",(t,n,s)=>{let a=t(n,s);return !a.success&&n==null&&(a.success=true,a.data=n),a}),this},default(t){return h(this,"_parse",(n,s,a)=>(s===void 0&&(s=t,s=typeof t=="function"?t():t),n(s,a))),this},pipe(t){return h(this,"_parse",(n,s,a)=>{let p=n(s,a);return p.success?t._parse(p.data,a):p}),this},refine(t,{message:n,type:s}={}){return s&&(n=n||T(s),o=s),h(this,"_parse",(a,p,f)=>{let m=a(p,f);d(f);if(!m.success)return m;let l=t(m.data);if(l===true||typeof l=="object"&&l?.success===true)return m;let b={message:typeof n=="function"?n(p):n};return {success:false,error:b,errors:[b]}}),this}};return y.length>0?y.reduce((t,n)=>n(t,e,c)??t,r):r}var y=[];function x(e){y.push(e);}x((e,o,i)=>{if(i?.type==="number"){let u=e;u.min=function(c,{message:r}={}){return this.refine(t=>t>=c,{message:r||`Number must be greater than or equal to ${c}.`})},u.max=function(c,{message:r}={}){return this.refine(t=>t<=c,{message:r||`Number must be less than or equal to ${c}.`})},u.positive=function({message:c}={}){return this.refine(r=>r>0,{message:c||"Number must be positive."})},u.negative=function({message:c}={}){return this.refine(r=>r<0,{message:c||"Number must be negative."})},u.int=function({message:c}={}){return this.refine(r=>Number.isInteger(r),{message:c||"Number must be an integer."})},u.float=function({message:c}={}){return this.refine(r=>Number.isFinite(r)&&!Number.isInteger(r),{message:c||"Number must be a floating point (non-integer)."})},u.multipleOf=function(c,{message:r}={}){return this.refine(t=>t%c===0,{message:r||`Number must be a multiple of ${c}.`})},u.finite=function({message:c}={}){return this.refine(r=>Number.isFinite(r),{message:c||"Number must be finite."})};}return e});exports.extend=x;exports.hookOriginal=h;exports.s=A;
@@ -0,0 +1,14 @@
1
+ export { ArraySchemaInterface, BooleanSchemaInterface, DateSchemaInterface, EnumSchemaInterface, ErrorStructure, ExtenderType, Infer, Invalid, NumberSchemaInterface, ObjectSchemaInterface, SchemaInterface, SchemaInterfaceOptions, SchemaType, StringSchemaInterface, UnionSchemaInterface, Valid, extend, hookOriginal, s } from './index.cjs';
2
+
3
+ declare module './index.ts' {
4
+ interface NumberSchemaInterface {
5
+ min(value: number, options?: SchemaInterfaceOptions): NumberSchemaInterface;
6
+ max(value: number, options?: SchemaInterfaceOptions): NumberSchemaInterface;
7
+ positive(options?: SchemaInterfaceOptions): NumberSchemaInterface;
8
+ negative(options?: SchemaInterfaceOptions): NumberSchemaInterface;
9
+ int(options?: SchemaInterfaceOptions): NumberSchemaInterface;
10
+ float(options?: SchemaInterfaceOptions): NumberSchemaInterface;
11
+ multipleOf(value: number, options?: SchemaInterfaceOptions): NumberSchemaInterface;
12
+ finite(options?: SchemaInterfaceOptions): NumberSchemaInterface;
13
+ }
14
+ }
@@ -0,0 +1,14 @@
1
+ export { ArraySchemaInterface, BooleanSchemaInterface, DateSchemaInterface, EnumSchemaInterface, ErrorStructure, ExtenderType, Infer, Invalid, NumberSchemaInterface, ObjectSchemaInterface, SchemaInterface, SchemaInterfaceOptions, SchemaType, StringSchemaInterface, UnionSchemaInterface, Valid, extend, hookOriginal, s } from './index.js';
2
+
3
+ declare module './index.ts' {
4
+ interface NumberSchemaInterface {
5
+ min(value: number, options?: SchemaInterfaceOptions): NumberSchemaInterface;
6
+ max(value: number, options?: SchemaInterfaceOptions): NumberSchemaInterface;
7
+ positive(options?: SchemaInterfaceOptions): NumberSchemaInterface;
8
+ negative(options?: SchemaInterfaceOptions): NumberSchemaInterface;
9
+ int(options?: SchemaInterfaceOptions): NumberSchemaInterface;
10
+ float(options?: SchemaInterfaceOptions): NumberSchemaInterface;
11
+ multipleOf(value: number, options?: SchemaInterfaceOptions): NumberSchemaInterface;
12
+ finite(options?: SchemaInterfaceOptions): NumberSchemaInterface;
13
+ }
14
+ }
package/dist/number.js ADDED
@@ -0,0 +1 @@
1
+ import'./chunk-QEBUM44M.js';export{c as extend,b as hookOriginal,a as s}from'./chunk-5ARMWSHU.js';
@@ -0,0 +1 @@
1
+ 'use strict';var P={abortEarly:true};function I(t,o){if(!o)return t;let i=t?.cause?.key?`${o}.${t.cause.key}`:`${o}`;return {message:`Error parsing key "${i}": ${t.message}`,cause:{key:i}}}function O(t,o,i){!t.errors||t.errors.length===0||t.errors.forEach(c=>{let r=I(c,i);o.push(r);});}function d(t){return {...P,...t}}var k=t=>typeof t=="string"||t instanceof String,w=t=>typeof t=="number"||t instanceof Number,E=t=>t===true||t===false,$=t=>t instanceof Date&&!Number.isNaN(t.getTime()),_=t=>Array.isArray(t),b=t=>typeof t=="object"&&t!==null&&!Array.isArray(t),A={object(t,o){let i=S(b,{...o,type:"object"});return i._getDescription=()=>`object({ ${Object.entries(t).map(([r,n])=>`${r}: ${n._getDescription()}`).join(", ")} })`,h(i,"_parse",(c,r,n)=>{let e=c(r,n),{abortEarly:a}=d(n);if(e.success===false)return e;let u={},s=[];for(let p in t){let f=t[p]._parse(e.data[p],n);if(f.success)u[p]=f.data;else {if(f=f,a!==false){let m=I(f.error,p);return {success:false,error:m,errors:[m]}}O(f,s,p);}}return s.length>0?{success:false,error:s[0],errors:s}:{success:true,data:u}}),i},string(t){return S(k,{...t,type:"string"})},number(t){return S(w,{...t,type:"number"})},boolean(t){return S(E,{...t,type:"boolean"})},date(t){return S($,{...t,type:"date"})},enum(t,o){let i=e=>t.includes(e),c=e=>`Invalid ${r} value. Expected ${t.map(a=>`"${a}"`).join(" | ")}, received "${e}".`,r="enum",n=S(i,{message:c,...o,type:r});return n._getDescription=()=>`enum(${t.map(e=>`"${e}"`).join(" | ")})`,n},array(t,o){let i=S(_,{...o,type:"array"});return i._getDescription=()=>`array(${t._getDescription()})`,h(i,"_parse",(c,r,n)=>{let e=c(r,n),{abortEarly:a}=d(n);if(e.success===false)return e;let u=[],s=[];for(let p=0;p<e.data.length;p++){let f=t._parse(e.data[p],n);if(f.success)u.push(f.data);else {if(f=f,a!==false){let m=I(f.error,p);return {success:false,error:m,errors:[m]}}O(f,s,p);}}return s.length>0?{success:false,error:s[0],errors:s}:{success:true,data:u}}),i},any(){return S(()=>true)},preprocess(t,o){return h(o,"_parse",(i,c)=>(c=t(c),i(c))),o},union(t,o){return S(n=>{for(let e=0;e<t.length;e++){let a=t[e]._parse(n);if(a.success)return a}return false},{message:n=>`Invalid union value. Expected the value to match one of the schemas:${t.map((e,a)=>` ${a+1}. ${e._getDescription()}`).join(",")} but received "${typeof n}" with value: ${b(n)?JSON.stringify(n):`"${n}"`}`,...o,type:"union"})}};function T(t){return o=>`The value "${o}" must be type of ${t} but is type of "${typeof o}".`}function h(t,o,i){let c=t[o];t[o]=(...r)=>i(c,...r);}function S(t,{type:o="any",name:i,message:c}={}){c=c||T(o);let r={name:i,message:c,type:o},n={_getName(){return i},_getType(){return o},_getDescription(){return this._getName()??this._getType()},_parse(e,a){let u=t(e);if(u===true)return {success:true,data:e};if(typeof u=="object"&&u?.success===true)return u;let s={message:typeof c=="function"?c(e):c};return {success:false,error:s,errors:[s]}},parse(e,a){let u=n._parse(e,a);if(!u.success)throw u=u,new Error(u.error.message,{cause:u.error.cause});return u.data},safeParse(e,a){return n._parse(e,a)},transform(e){return h(this,"_parse",(a,u,s)=>{let p=a(u,s);return p.success&&(p.data=e(p.data)),p}),this},optional(){return h(this,"_parse",(e,a,u)=>{let s=e(a,u);return !s.success&&a===void 0&&(s.data=void 0,s.success=true),s}),this},nullable(){return h(this,"_parse",(e,a,u)=>{let s=e(a,u);return !s.success&&a===null&&(s.data=null,s.success=true),s}),this},nullish(){return h(this,"_parse",(e,a,u)=>{let s=e(a,u);return !s.success&&a==null&&(s.success=true,s.data=a),s}),this},default(e){return h(this,"_parse",(a,u,s)=>(u===void 0&&(u=e,u=typeof e=="function"?e():e),a(u,s))),this},pipe(e){return h(this,"_parse",(a,u,s)=>{let p=a(u,s);return p.success?e._parse(p.data,s):p}),this},refine(e,{message:a,type:u}={}){return u&&(a=a||T(u),o=u),h(this,"_parse",(s,p,f)=>{let m=s(p,f);d(f);if(!m.success)return m;let l=e(m.data);if(l===true||typeof l=="object"&&l?.success===true)return m;let g={message:typeof a=="function"?a(p):a};return {success:false,error:g,errors:[g]}}),this}};return y.length>0?y.reduce((e,a)=>a(e,t,r)??e,n):n}var y=[];function x(t){y.push(t);}x((t,o,i)=>{if(i?.type==="string"){let c=t;c.min=function(r,{message:n}={}){return this.refine(e=>e.length>=r,{message:n||(e=>`String must be at least ${r} characters long (received ${e.length} characters: "${e}")`)})},c.max=function(r,{message:n}={}){return this.refine(e=>e.length<=r,{message:n||(e=>`String must be at most ${r} characters long (received ${e.length} characters: "${e}")`)})},c.length=function(r,{message:n}={}){return this.refine(e=>e.length===r,{message:n||(e=>`String must be exactly ${r} characters long (received ${e.length} characters: "${e}")`)})},c.nonEmpty=function({message:r}={}){return this.refine(n=>n.length>0,{message:r||"String must not be empty (received empty string)"})},c.startsWith=function(r,{message:n}={}){return this.refine(e=>e.startsWith(r),{message:n||(e=>`String must start with "${r}" (received: "${e}")`)})},c.endsWith=function(r,{message:n}={}){return this.refine(e=>e.endsWith(r),{message:n||(e=>`String must end with "${r}" (received: "${e}")`)})},c.includes=function(r,{message:n}={}){return this.refine(e=>e.includes(r),{message:n||(e=>`String must include "${r}" (received: "${e}")`)})},c.toLowerCase=function(){return this.transform(r=>r.toLowerCase())},c.toUpperCase=function(){return this.transform(r=>r.toUpperCase())},c.trim=function(){return this.transform(r=>r.trim())},c.padStart=function(r,n=" "){return this.transform(e=>e.padStart(r,n))},c.padEnd=function(r,n=" "){return this.transform(e=>e.padEnd(r,n))},c.replace=function(r,n){return this.transform(e=>e.replace(r,n))};}return t});exports.extend=x;exports.hookOriginal=h;exports.s=A;
@@ -0,0 +1,19 @@
1
+ export { ArraySchemaInterface, BooleanSchemaInterface, DateSchemaInterface, EnumSchemaInterface, ErrorStructure, ExtenderType, Infer, Invalid, NumberSchemaInterface, ObjectSchemaInterface, SchemaInterface, SchemaInterfaceOptions, SchemaType, StringSchemaInterface, UnionSchemaInterface, Valid, extend, hookOriginal, s } from './index.cjs';
2
+
3
+ declare module './index.ts' {
4
+ interface StringSchemaInterface {
5
+ min(length: number, options?: SchemaInterfaceOptions): StringSchemaInterface;
6
+ max(length: number, options?: SchemaInterfaceOptions): StringSchemaInterface;
7
+ length(length: number, options?: SchemaInterfaceOptions): StringSchemaInterface;
8
+ nonEmpty(options?: SchemaInterfaceOptions): StringSchemaInterface;
9
+ startsWith(prefix: string, options?: SchemaInterfaceOptions): StringSchemaInterface;
10
+ endsWith(suffix: string, options?: SchemaInterfaceOptions): StringSchemaInterface;
11
+ includes(substring: string, options?: SchemaInterfaceOptions): StringSchemaInterface;
12
+ trim(): StringSchemaInterface;
13
+ toLowerCase(): StringSchemaInterface;
14
+ toUpperCase(): StringSchemaInterface;
15
+ padStart(length: number, fillChar?: string): StringSchemaInterface;
16
+ padEnd(length: number, fillChar?: string): StringSchemaInterface;
17
+ replace(search: string | RegExp, replace: string): StringSchemaInterface;
18
+ }
19
+ }
@@ -0,0 +1,19 @@
1
+ export { ArraySchemaInterface, BooleanSchemaInterface, DateSchemaInterface, EnumSchemaInterface, ErrorStructure, ExtenderType, Infer, Invalid, NumberSchemaInterface, ObjectSchemaInterface, SchemaInterface, SchemaInterfaceOptions, SchemaType, StringSchemaInterface, UnionSchemaInterface, Valid, extend, hookOriginal, s } from './index.js';
2
+
3
+ declare module './index.ts' {
4
+ interface StringSchemaInterface {
5
+ min(length: number, options?: SchemaInterfaceOptions): StringSchemaInterface;
6
+ max(length: number, options?: SchemaInterfaceOptions): StringSchemaInterface;
7
+ length(length: number, options?: SchemaInterfaceOptions): StringSchemaInterface;
8
+ nonEmpty(options?: SchemaInterfaceOptions): StringSchemaInterface;
9
+ startsWith(prefix: string, options?: SchemaInterfaceOptions): StringSchemaInterface;
10
+ endsWith(suffix: string, options?: SchemaInterfaceOptions): StringSchemaInterface;
11
+ includes(substring: string, options?: SchemaInterfaceOptions): StringSchemaInterface;
12
+ trim(): StringSchemaInterface;
13
+ toLowerCase(): StringSchemaInterface;
14
+ toUpperCase(): StringSchemaInterface;
15
+ padStart(length: number, fillChar?: string): StringSchemaInterface;
16
+ padEnd(length: number, fillChar?: string): StringSchemaInterface;
17
+ replace(search: string | RegExp, replace: string): StringSchemaInterface;
18
+ }
19
+ }
package/dist/string.js ADDED
@@ -0,0 +1 @@
1
+ import'./chunk-6ABUMTDR.js';export{c as extend,b as hookOriginal,a as s}from'./chunk-5ARMWSHU.js';
@@ -0,0 +1,138 @@
1
+ import { s } from '../src/full.ts';
2
+
3
+ // Advanced form validation example
4
+ const addressSchema = s.object({
5
+ street: s.string().min(3).max(100),
6
+ city: s.string().min(2).max(50),
7
+ zipCode: s.string().length(5).refine((val) => /^\d{5}$/.test(val), {
8
+ message: 'ZIP code must be 5 digits',
9
+ }),
10
+ country: s.enum(['US', 'CA', 'MX', 'UK']),
11
+ });
12
+
13
+ const userProfileSchema = s.object({
14
+ // Personal info
15
+ firstName: s.string().trim().min(2).max(50),
16
+ lastName: s.string().trim().min(2).max(50),
17
+ email: s
18
+ .string()
19
+ .trim()
20
+ .toLowerCase()
21
+ .refine((value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value), {
22
+ message: 'Invalid email address',
23
+ }),
24
+
25
+ // Contact
26
+ phone: s
27
+ .string()
28
+ .optional()
29
+ .transform((val) => val?.replace(/\D/g, ''))
30
+ .refine((val) => !val || val.length === 10, {
31
+ message: 'Phone number must be 10 digits',
32
+ }),
33
+
34
+ // Address
35
+ address: addressSchema.optional(),
36
+
37
+ // Preferences
38
+ role: s.enum(['admin', 'editor', 'viewer']).default('viewer'),
39
+ tags: s.array(s.string()).min(1).max(5).unique(),
40
+ isActive: s.boolean().default(true),
41
+
42
+ // Metadata
43
+ createdAt: s.date(),
44
+ updatedAt: s.date().optional(),
45
+ });
46
+
47
+ console.log('=== Valid User Profile ===');
48
+ const validProfile = {
49
+ firstName: ' John ',
50
+ lastName: 'Doe',
51
+ email: ' JOHN.DOE@EXAMPLE.COM ',
52
+ phone: '(555) 123-4567',
53
+ address: {
54
+ street: '123 Main St',
55
+ city: 'New York',
56
+ zipCode: '10001',
57
+ country: 'US' as const,
58
+ },
59
+ role: 'admin' as const,
60
+ tags: ['developer', 'typescript', 'node'],
61
+ isActive: true,
62
+ createdAt: new Date(),
63
+ };
64
+
65
+ console.log('Input:', validProfile);
66
+ console.log('Parsed:', userProfileSchema.parse(validProfile));
67
+
68
+ console.log('\n=== Invalid User Profile (missing required fields) ===');
69
+ const invalidProfile = {
70
+ firstName: 'John',
71
+ // lastName missing
72
+ email: 'invalid-email',
73
+ tags: [],
74
+ createdAt: new Date(),
75
+ };
76
+
77
+ console.log('Result:', userProfileSchema.safeParse(invalidProfile));
78
+
79
+ console.log('\n=== Invalid ZIP Code ===');
80
+ const invalidZipProfile = {
81
+ ...validProfile,
82
+ address: {
83
+ street: '123 Main St',
84
+ city: 'New York',
85
+ zipCode: 'ABC12', // Invalid: not digits
86
+ country: 'US' as const,
87
+ },
88
+ };
89
+
90
+ console.log('Result:', userProfileSchema.safeParse(invalidZipProfile));
91
+
92
+ // API response validation
93
+ const apiResponseSchema = s.object({
94
+ status: s.enum(['success', 'error']),
95
+ data: s
96
+ .object({
97
+ users: s.array(
98
+ s.object({
99
+ id: s.number().positive().int(),
100
+ name: s.string(),
101
+ email: s.string(),
102
+ }),
103
+ ),
104
+ total: s.number().positive().int(),
105
+ })
106
+ .optional(),
107
+ error: s
108
+ .object({
109
+ code: s.string(),
110
+ message: s.string(),
111
+ })
112
+ .optional(),
113
+ });
114
+
115
+ console.log('\n=== API Response Validation (Success) ===');
116
+ const successResponse = {
117
+ status: 'success' as const,
118
+ data: {
119
+ users: [
120
+ { id: 1, name: 'John', email: 'john@example.com' },
121
+ { id: 2, name: 'Jane', email: 'jane@example.com' },
122
+ ],
123
+ total: 2,
124
+ },
125
+ };
126
+
127
+ console.log('Result:', apiResponseSchema.parse(successResponse));
128
+
129
+ console.log('\n=== API Response Validation (Error) ===');
130
+ const errorResponse = {
131
+ status: 'error' as const,
132
+ error: {
133
+ code: 'NOT_FOUND',
134
+ message: 'User not found',
135
+ },
136
+ };
137
+
138
+ console.log('Result:', apiResponseSchema.parse(errorResponse));
@@ -0,0 +1,41 @@
1
+ import { s } from '../src/full.ts';
2
+
3
+ // String validations with transformations
4
+ console.log('=== String Validations ===');
5
+ console.log(
6
+ 'trim + startsWith + endsWith:',
7
+ s
8
+ .string()
9
+ .trim()
10
+ .startsWith('Hello')
11
+ .endsWith('World')
12
+ .safeParse(' Hello, World '),
13
+ );
14
+ console.log(
15
+ 'startsWith + endsWith (without trim):',
16
+ s.string().startsWith('Hello').endsWith('World').safeParse(' Hello, World '),
17
+ );
18
+
19
+ // Array validations
20
+ console.log('\n=== Array Validations ===');
21
+ console.log(
22
+ 'Array with min/max:',
23
+ s.array(s.string()).min(2).max(5).safeParse(['Hello', 'World']),
24
+ );
25
+
26
+ // Number validations
27
+ console.log('\n=== Number Validations ===');
28
+ console.log('Integer + positive:', s.number().int().positive().safeParse(42));
29
+ console.log('Float validation:', s.number().float().safeParse(3.14));
30
+
31
+ // Union with extended methods
32
+ console.log('\n=== Union Validations ===');
33
+ console.log(
34
+ 'Union with string validation:',
35
+ s
36
+ .union([
37
+ s.string({ message: 'stringSchema' }).startsWith('Hello'),
38
+ s.number(),
39
+ ])
40
+ .safeParse('World'),
41
+ );
@@ -0,0 +1,120 @@
1
+ import { s, extend, type StringSchemaInterface } from '../src/index.ts';
2
+
3
+ // Extend StringSchemaInterface to add custom methods
4
+ declare module '../src/index.ts' {
5
+ interface StringSchemaInterface {
6
+ email(options?: { message?: string }): StringSchemaInterface;
7
+ url(options?: { message?: string }): StringSchemaInterface;
8
+ uuid(options?: { message?: string }): StringSchemaInterface;
9
+ }
10
+ }
11
+
12
+ // Implement the extensions
13
+ extend((schema, _, options) => {
14
+ if (options?.type === 'string') {
15
+ const stringSchema = schema as StringSchemaInterface;
16
+
17
+ // Email validation
18
+ stringSchema.email = function ({ message } = {}) {
19
+ return this.refine(
20
+ (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
21
+ {
22
+ message: message || ((value: string) => `"${value}" is not a valid email address`),
23
+ },
24
+ );
25
+ };
26
+
27
+ // URL validation
28
+ stringSchema.url = function ({ message } = {}) {
29
+ return this.refine(
30
+ (value) => {
31
+ try {
32
+ new URL(value);
33
+ return true;
34
+ } catch {
35
+ return false;
36
+ }
37
+ },
38
+ {
39
+ message: message || ((value: string) => `"${value}" is not a valid URL`),
40
+ },
41
+ );
42
+ };
43
+
44
+ // UUID validation
45
+ stringSchema.uuid = function ({ message } = {}) {
46
+ return this.refine(
47
+ (value) =>
48
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(
49
+ value,
50
+ ),
51
+ {
52
+ message: message || ((value: string) => `"${value}" is not a valid UUID`),
53
+ },
54
+ );
55
+ };
56
+ }
57
+
58
+ return schema;
59
+ });
60
+
61
+ // Now use the extended schema
62
+ console.log('=== Email Validation ===');
63
+ const emailSchema = s.string().email();
64
+ console.log('Valid email:', emailSchema.safeParse('user@example.com'));
65
+ console.log('Invalid email:', emailSchema.safeParse('not-an-email'));
66
+ console.log('Empty string:', emailSchema.safeParse(''));
67
+
68
+ console.log('\n=== URL Validation ===');
69
+ const urlSchema = s.string().url();
70
+ console.log('Valid URL:', urlSchema.safeParse('https://example.com'));
71
+ console.log('Valid URL with path:', urlSchema.safeParse('https://example.com/path'));
72
+ console.log('Invalid URL:', urlSchema.safeParse('not a url'));
73
+
74
+ console.log('\n=== UUID Validation ===');
75
+ const uuidSchema = s.string().uuid();
76
+ console.log(
77
+ 'Valid UUID:',
78
+ uuidSchema.safeParse('550e8400-e29b-41d4-a716-446655440000'),
79
+ );
80
+ console.log('Invalid UUID:', uuidSchema.safeParse('not-a-uuid'));
81
+ console.log(
82
+ 'Invalid UUID format:',
83
+ uuidSchema.safeParse('550e8400-e29b-41d4-a716'),
84
+ );
85
+
86
+ // Combine extensions in a schema
87
+ console.log('\n=== Combined Schema ===');
88
+ const userSchema = s.object({
89
+ id: s.string().uuid(),
90
+ email: s.string().trim().toLowerCase().email(),
91
+ website: s.string().url().optional(),
92
+ });
93
+
94
+ console.log(
95
+ 'Valid user:',
96
+ userSchema.parse({
97
+ id: '550e8400-e29b-41d4-a716-446655440000',
98
+ email: ' USER@EXAMPLE.COM ',
99
+ website: 'https://example.com',
100
+ }),
101
+ );
102
+
103
+ console.log(
104
+ '\nInvalid user (bad email):',
105
+ userSchema.safeParse({
106
+ id: '550e8400-e29b-41d4-a716-446655440000',
107
+ email: 'not-an-email',
108
+ }),
109
+ );
110
+
111
+ // Custom error messages
112
+ console.log('\n=== Custom Error Messages ===');
113
+ const customEmailSchema = s
114
+ .string()
115
+ .email({ message: 'Please enter a valid email address' });
116
+
117
+ console.log(
118
+ 'Custom error:',
119
+ customEmailSchema.safeParse('invalid'),
120
+ );
@@ -0,0 +1,110 @@
1
+ import { s } from '../src/full.ts';
2
+
3
+ // Custom email validation using refine
4
+ const emailSchema = s
5
+ .string()
6
+ .trim()
7
+ .toLowerCase()
8
+ .refine((value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value), {
9
+ message: 'Invalid email address format',
10
+ });
11
+
12
+ console.log('=== Custom Email Validation ===');
13
+ console.log('Valid email:', emailSchema.safeParse('user@example.com'));
14
+ console.log('Invalid email:', emailSchema.safeParse('not-an-email'));
15
+ console.log(
16
+ 'Email with whitespace:',
17
+ emailSchema.safeParse(' User@Example.COM '),
18
+ );
19
+
20
+ // Custom URL validation
21
+ const urlSchema = s.string().refine(
22
+ (value) => {
23
+ try {
24
+ new URL(value);
25
+ return true;
26
+ } catch {
27
+ return false;
28
+ }
29
+ },
30
+ {
31
+ message: (value) => `"${value}" is not a valid URL`,
32
+ },
33
+ );
34
+
35
+ console.log('\n=== Custom URL Validation ===');
36
+ console.log('Valid URL:', urlSchema.safeParse('https://example.com'));
37
+ console.log('Invalid URL:', urlSchema.safeParse('not a url'));
38
+
39
+ // Custom age validation
40
+ const ageSchema = s
41
+ .number()
42
+ .int()
43
+ .positive()
44
+ .refine((value) => value >= 18, {
45
+ message: (value) =>
46
+ `Age must be at least 18 (received ${value} years old)`,
47
+ })
48
+ .refine((value) => value <= 120, {
49
+ message: (value) => `Age seems unrealistic (received ${value} years old)`,
50
+ });
51
+
52
+ console.log('\n=== Custom Age Validation ===');
53
+ console.log('Valid age:', ageSchema.safeParse(25));
54
+ console.log('Too young:', ageSchema.safeParse(15));
55
+ console.log('Too old:', ageSchema.safeParse(150));
56
+
57
+ // Custom password validation
58
+ const passwordSchema = s
59
+ .string()
60
+ .min(8, { message: 'Password must be at least 8 characters' })
61
+ .refine((value) => /[A-Z]/.test(value), {
62
+ message: 'Password must contain at least one uppercase letter',
63
+ })
64
+ .refine((value) => /[a-z]/.test(value), {
65
+ message: 'Password must contain at least one lowercase letter',
66
+ })
67
+ .refine((value) => /[0-9]/.test(value), {
68
+ message: 'Password must contain at least one number',
69
+ })
70
+ .refine((value) => /[^A-Za-z0-9]/.test(value), {
71
+ message: 'Password must contain at least one special character',
72
+ });
73
+
74
+ console.log('\n=== Custom Password Validation ===');
75
+ console.log('Valid password:', passwordSchema.safeParse('MyP@ssw0rd'));
76
+ console.log('Too short:', passwordSchema.safeParse('Pass1!'));
77
+ console.log('No uppercase:', passwordSchema.safeParse('myp@ssw0rd'));
78
+ console.log('No special char:', passwordSchema.safeParse('MyPassw0rd'));
79
+
80
+ // Custom object validation with cross-field checks
81
+ const registrationSchema = s
82
+ .object({
83
+ password: s.string().min(8),
84
+ confirmPassword: s.string(),
85
+ email: emailSchema,
86
+ age: ageSchema,
87
+ })
88
+ .refine((data) => data.password === data.confirmPassword, {
89
+ message: 'Passwords do not match',
90
+ });
91
+
92
+ console.log('\n=== Cross-Field Validation ===');
93
+ console.log(
94
+ 'Valid registration:',
95
+ registrationSchema.safeParse({
96
+ password: 'MyP@ssw0rd',
97
+ confirmPassword: 'MyP@ssw0rd',
98
+ email: 'user@example.com',
99
+ age: 25,
100
+ }),
101
+ );
102
+ console.log(
103
+ 'Password mismatch:',
104
+ registrationSchema.safeParse({
105
+ password: 'MyP@ssw0rd',
106
+ confirmPassword: 'Different123!',
107
+ email: 'user@example.com',
108
+ age: 25,
109
+ }),
110
+ );
package/package.json CHANGED
@@ -1,15 +1,21 @@
1
1
  {
2
2
  "name": "@esmj/schema",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Tiny extendable package for schema validation.",
5
+ "type": "module",
5
6
  "keywords": [
6
7
  "schema",
7
8
  "validation",
8
9
  "type",
10
+ "typescript",
11
+ "type-safe",
12
+ "validator",
13
+ "parse",
14
+ "transform",
9
15
  "inference",
10
16
  "zod",
11
- "@sinclair/typebox",
12
17
  "yup",
18
+ "@sinclair/typebox",
13
19
  "superstruct",
14
20
  "joi",
15
21
  "arktype"
@@ -18,11 +24,37 @@
18
24
  "module": "dist/index",
19
25
  "exports": {
20
26
  ".": {
27
+ "types": "./dist/index.d.ts",
21
28
  "import": "./dist/index.mjs",
22
29
  "require": "./dist/index.js"
30
+ },
31
+ "./full": {
32
+ "types": "./dist/full.d.ts",
33
+ "import": "./dist/full.mjs",
34
+ "require": "./dist/full.js"
35
+ },
36
+ "./string": {
37
+ "types": "./dist/string.d.ts",
38
+ "import": "./dist/string.mjs",
39
+ "require": "./dist/string.js"
40
+ },
41
+ "./number": {
42
+ "types": "./dist/number.d.ts",
43
+ "import": "./dist/number.mjs",
44
+ "require": "./dist/number.js"
45
+ },
46
+ "./array": {
47
+ "types": "./dist/array.d.ts",
48
+ "import": "./dist/array.mjs",
49
+ "require": "./dist/array.js"
23
50
  }
24
51
  },
25
- "sideEffects": false,
52
+ "sideEffects": [
53
+ "src/string.ts",
54
+ "src/number.ts",
55
+ "src/array.ts",
56
+ "src/full.ts"
57
+ ],
26
58
  "typings": "dist/index.d.ts",
27
59
  "scripts": {
28
60
  "lint": "biome check --no-errors-on-unmatched",
@@ -70,22 +102,20 @@
70
102
  "@biomejs/biome": "1.9.4",
71
103
  "@commitlint/cli": "^19.8.1",
72
104
  "@commitlint/config-conventional": "^19.8.1",
73
- "@sinclair/typebox": "^0.34.38",
74
- "@typescript-eslint/eslint-plugin": "^8.33.0",
75
- "@typescript-eslint/parser": "^8.33.0",
105
+ "@sinclair/typebox": "^0.34.41",
76
106
  "@zod/mini": "^4.0.0-beta.0",
77
- "arktype": "^2.1.20",
107
+ "arktype": "^2.1.22",
78
108
  "commitizen": "^4.3.1",
79
109
  "conventional-changelog-cli": "^5.0.0",
80
110
  "cz-conventional-changelog": "^3.3.0",
81
- "effect": "^3.17.6",
111
+ "effect": "^3.17.13",
82
112
  "git-cz": "^4.9.0",
83
113
  "husky": "^9.1.7",
84
114
  "joi": "^17.13.3",
85
- "lint-staged": "^16.1.0",
115
+ "lint-staged": "^16.1.6",
86
116
  "superstruct": "^2.0.2",
87
117
  "tsup": "^8.5.0",
88
- "yup": "^1.6.1",
118
+ "yup": "^1.7.0",
89
119
  "zod": "^3.25.42"
90
120
  }
91
121
  }
package/tsconfig.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2022",
4
+ "module": "es2022",
5
+ "moduleResolution": "node",
6
+ "esModuleInterop": true,
7
+ "allowImportingTsExtensions": true,
8
+ "resolveJsonModule": true,
9
+ "noEmit": true
10
+ },
11
+ "include": ["src/**/*"]
12
+ }