@esmj/schema 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,11 +24,50 @@ When choosing a schema validation library, bundle size can be an important facto
24
24
 
25
25
  | Library | Bundle Size (minified + gzipped) |
26
26
  |------------------|---------------------------------|
27
- | `@esmj/schema` | ~1 KB |
28
- | Superstruct | ~3,2 KB |
29
- | Yup | ~12,2 KB |
30
- | Zod | ~14 KB |
31
- | Joi | ~40 KB |
27
+ | `@esmj/schema` | `~1 KB` |
28
+ | Superstruct | ~3.2 KB |
29
+ | Yup | ~12.2 KB |
30
+ | Zod@3 | ~13 KB |
31
+ | @zod/mini | ~20,5 KB |
32
+ | Joi | ~40,4 KB |
33
+ | Zod@4 | ~40,8 KB |
34
+
35
+ ### Performance Comparison
36
+
37
+ #### Schema Creation Performance
38
+
39
+ | Library | 1 Schema | 1,000 Schema | 1,000,000 Schema |
40
+ |-----------------|-------------|------------------|----------------------|
41
+ | `@esmj/schema` | `0.02 ms` | 4.93 ms | `399.62 ms` |
42
+ | zod@3 | 0.08 ms | 9.68 ms | 8.53 s |
43
+ | @zod/mini | 0.22 ms | 39.77 ms | 34.51 s |
44
+ | Yup | 0.54 ms | 14.03 ms | 12.34 s |
45
+ | Superstruct | 0.13 ms | `1.04 ms` | 487.94 ms |
46
+ | Joi | 0.62 ms | 31.60 ms | 23.06 s |
47
+
48
+ #### Parsing Performance
49
+
50
+ | Library | 1 Iteration | 1,000 Iterations | 1,000,000 Iterations |
51
+ |-----------------|-------------|------------------|----------------------|
52
+ | `@esmj/schema` | `0.05 ms` | 0.46 ms | 267.93 ms |
53
+ | zod@3 | 0.14 ms | 1.44 ms | 897.89 ms |
54
+ | @zod/mini | 0.23 ms | `0.42 ms` | `199.08 ms` |
55
+ | Yup | 0.30 ms | 9.49 ms | 8.69 s |
56
+ | Superstruct | 0.08 ms | 4.18 ms | 3.71 s |
57
+ | Joi | 0.33 ms | 3.35 ms | 2.69 s |
58
+
59
+ #### Error Handling Performance
60
+
61
+ | Library | 1 Iteration | 1,000 Iterations | 1,000,000 Iterations |
62
+ |-----------------|-------------|------------------|----------------------|
63
+ | `@esmj/schema` | `0.03 ms` | `0.59 ms` | `365.32 ms` |
64
+ | zod3 | 0.05 ms | 2.09 ms | 1.26 s |
65
+ | @zod/mini | 0.07 ms | 0.99 ms | 545.12 ms |
66
+ | Yup | 0.27 ms | 19.28 ms | 18.87 s |
67
+ | Superstruct | 0.04 ms | 8.62 ms | 6.24 s |
68
+ | Joi | 0.15 ms | 4.13 ms | 2.57 s |
69
+
70
+ **Note:** During the performance tests, `@zod/mini` was observed to consume 200% CPU, while other libraries used only 100% CPU. This may affect the interpretation of the results, especially in multi-threaded environments.
32
71
 
33
72
  ## Usage
34
73
 
@@ -180,9 +219,26 @@ const result = stringSchema.parse('hello');
180
219
  Safely parses the given value according to the schema, returning a success or error result.
181
220
 
182
221
  ```typescript
183
- const result = stringSchema.safeParse('hello'); // { success: true, data: 'hello' }
222
+ const result = stringSchema.safeParse('hello');
223
+ // { success: true, data: 'hello' }
224
+
225
+ const errorResult = stringSchema.safeParse(123);
226
+ // { success: false, error: { message: 'The value "123" must be type of string but is type of "number".' } }
184
227
  ```
185
228
 
229
+ **Note:** The `error` returned by `safeParse` is not a native `Error` instance. Instead, it is a plain object with the following structure:
230
+
231
+ ```typescript
232
+ type ErrorStructure = {
233
+ message: string;
234
+ cause?: {
235
+ key?: string;
236
+ };
237
+ };
238
+ ```
239
+
240
+ This allows for easier serialization and debugging but may require additional handling if you expect a native `Error` instance.
241
+
186
242
  #### `optional()`
187
243
 
188
244
  Makes the schema optional.
package/dist/index.d.mts CHANGED
@@ -1,12 +1,22 @@
1
+ type ErrorStructure = {
2
+ message: string;
3
+ cause?: {
4
+ key?: string;
5
+ };
6
+ };
7
+ type Valid<Output> = {
8
+ success: true;
9
+ data: Output;
10
+ };
11
+ type Invalid = {
12
+ success: false;
13
+ error: ErrorStructure;
14
+ };
15
+ type InternalParseOutput<Output> = Valid<Output> | Invalid;
1
16
  interface SchemaInterface<Input, Output> {
17
+ _parse(value: Input | Partial<Input> | unknown): InternalParseOutput<Output>;
2
18
  parse(value: Input | Partial<Input> | unknown): Output;
3
- safeParse(value: Input | Partial<Input> | unknown): {
4
- success: true;
5
- data: Output;
6
- } | {
7
- success: false;
8
- error: Error;
9
- };
19
+ safeParse(value: Input | Partial<Input> | unknown): InternalParseOutput<Output>;
10
20
  optional(): SchemaInterface<Input, Partial<Output> | undefined>;
11
21
  transform<NewOutput>(callback: (value: Input) => NewOutput): SchemaInterface<Input, NewOutput>;
12
22
  nullable(): SchemaInterface<Input, Output | null>;
package/dist/index.d.ts CHANGED
@@ -1,12 +1,22 @@
1
+ type ErrorStructure = {
2
+ message: string;
3
+ cause?: {
4
+ key?: string;
5
+ };
6
+ };
7
+ type Valid<Output> = {
8
+ success: true;
9
+ data: Output;
10
+ };
11
+ type Invalid = {
12
+ success: false;
13
+ error: ErrorStructure;
14
+ };
15
+ type InternalParseOutput<Output> = Valid<Output> | Invalid;
1
16
  interface SchemaInterface<Input, Output> {
17
+ _parse(value: Input | Partial<Input> | unknown): InternalParseOutput<Output>;
2
18
  parse(value: Input | Partial<Input> | unknown): Output;
3
- safeParse(value: Input | Partial<Input> | unknown): {
4
- success: true;
5
- data: Output;
6
- } | {
7
- success: false;
8
- error: Error;
9
- };
19
+ safeParse(value: Input | Partial<Input> | unknown): InternalParseOutput<Output>;
10
20
  optional(): SchemaInterface<Input, Partial<Output> | undefined>;
11
21
  transform<NewOutput>(callback: (value: Input) => NewOutput): SchemaInterface<Input, NewOutput>;
12
22
  nullable(): SchemaInterface<Input, Output | null>;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- 'use strict';var y={object(n){let u=i(c=>typeof c=="object"&&c!==null&&!Array.isArray(c),{type:"object"});return s(u,"parse",(c,...o)=>{let e=c(...o);return n&&typeof e=="object"?Object.keys(n).reduce((r,t)=>{if(typeof n[t]?.parse=="function")try{r[t]=n[t].parse(e[t]);}catch(p){throw t=p.cause?.key?`${t}.${p.cause.key}`:t,new Error(`Error parsing key "${t}": ${p.message}`,{cause:{key:t}})}return r},{}):e}),u},string(){return i(a=>typeof a=="string",{type:"string"})},number(){return i(a=>typeof a=="number",{type:"number"})},boolean(){return i(a=>a===true||a===false,{type:"boolean"})},date(){return i(a=>a instanceof Date&&!Number.isNaN(a.getTime()),{type:"date"})},enum(n){let a=e=>n.includes(e),u=e=>`Invalid ${c} value. Expected ${n.map(r=>`"${r}"`).join(" | ")}, received "${e}".`,c="enum";return i(a,{type:c,message:u})},array(n){let u=i(c=>Array.isArray(c),{type:"array"});return s(u,"parse",(c,o)=>(o=c(o),n&&Array.isArray(o)?o.map((e,r)=>{try{return n.parse(e)}catch(t){let p=t.cause?.key?`${r}.${t.cause.key}`:r;throw new Error(`Error parsing index "${r}": ${t.message}`,{cause:{key:p}})}}):o)),u},any(){return i(()=>true)},preprocess(n,a){return s(a,"parse",(u,c)=>(c=n(c),u(c))),a}};function h(n){return a=>`The value "${a}" must be type of ${n} but is type of "${typeof a}".`}function s(n,a,u){let c=n[a];n[a]=(...o)=>u(c,...o);}function i(n,{type:a="any",message:u}={}){u=u||h(a);let c={message:u,type:a},o={parse(e){if(!n(e))throw new Error(u(e));return e},safeParse(e){try{return {success:!0,data:this.parse(e)}}catch(r){return {success:false,error:r}}},transform(e){return s(this,"parse",(r,t)=>e(r(t))),this},optional(){return s(this,"parse",(e,r)=>{try{return e(r)}catch{return}}),this},nullable(){return s(this,"parse",(e,r)=>{try{return e(r)}catch{return null}}),this},nullish(){return s(this,"parse",(e,r)=>{try{return e(r)}catch{return r==null?r:null}}),this},default(e){return s(this,"parse",(r,t)=>(t===void 0&&(t=e,t=typeof e=="function"?e():e),t=r(t),t)),this},pipe(e){return s(this,"parse",(r,t)=>e.parse(r(t))),this},refine(e,{message:r}){return s(this,"parse",(t,p)=>{let f=t(p);if(!e(f))throw new Error(r);return f}),this}};return m.reduce((e,r)=>r(e,n,c)??e,o)}var m=[];function I(n){m.push(n);}exports.extend=I;exports.s=y;
1
+ 'use strict';var h=r=>typeof r=="string"||r instanceof String,I=r=>typeof r=="number"||r instanceof Number,l=r=>r===true||r===false,y=r=>r instanceof Date&&!Number.isNaN(r.getTime()),d=r=>Array.isArray(r),S=r=>typeof r=="object"&&r!==null&&!Array.isArray(r),g={object(r){let a=p(S,{type:"object"});return i(a,"_parse",(u,...o)=>{let c=u(...o);if(c.success===false)return c;let n={};for(let t in r){let e=r[t]._parse(c.data[t]);if(e.success)n[t]=e.data;else {e=e;let s=e?.error?.cause?.key?`${t}.${e?.error?.cause?.key}`:t;return e.error.message=`Error parsing key "${s}": ${e.error.message}`,e.error.cause={key:s},e}}return {success:true,data:n}}),a},string(){return p(h,{type:"string"})},number(){return p(I,{type:"number"})},boolean(){return p(l,{type:"boolean"})},date(){return p(y,{type:"date"})},enum(r){let a=n=>r.includes(n),u=n=>`Invalid ${o} value. Expected ${r.map(t=>`"${t}"`).join(" | ")}, received "${n}".`,o="enum";return p(a,{type:o,message:u})},array(r){let a=p(d,{type:"array"});return i(a,"_parse",(u,...o)=>{let c=u(...o);if(c.success===false)return c;let n=[];for(let t=0;t<c.data.length;t++){let e=r._parse(c.data[t]);if(e.success)n.push(e.data);else {e=e;let s=e.error?.cause?.key?`${t}.${e.error.cause.key}`:`${t}`;return e.error.message=`Error parsing index "${t}": ${e.error.message}`,e.error.cause={key:s},e}}return {success:true,data:n}}),a},any(){return p(()=>true)},preprocess(r,a){return i(a,"_parse",(u,o)=>(o=r(o),u(o))),a}};function b(r){return a=>`The value "${a}" must be type of ${r} but is type of "${typeof a}".`}function i(r,a,u){let o=r[a];r[a]=(...c)=>u(o,...c);}function p(r,{type:a="any",message:u}={}){u=u||b(a);let o={message:u,type:a},c={_parse(n){return r(n)?{success:true,data:n}:{success:false,error:{message:u(n)}}},parse(n){let t=c._parse(n);if(!t.success)throw t=t,new Error(t.error.message,{cause:t.error.cause});return t.data},safeParse(n){return c._parse(n)},transform(n){return i(this,"_parse",(t,e)=>{let s=t(e);return s.success&&(s.data=n(s.data)),s}),this},optional(){return i(this,"_parse",(n,t)=>{let e=n(t);return e.success||(e.data=void 0,e.success=true),e}),this},nullable(){return i(this,"_parse",(n,t)=>{let e=n(t);return e.success||(e.data=null,e.success=true),e}),this},nullish(){return i(this,"_parse",(n,t)=>{let e=n(t);return !e.success&&t==null&&(e.success=true,e.data=t),e}),this},default(n){return i(this,"_parse",(t,e)=>(e===void 0&&(e=n,e=typeof n=="function"?n():n),t(e))),this},pipe(n){return i(this,"_parse",(t,e)=>{let s=t(e);return s.success?n._parse(s.data):s}),this},refine(n,{message:t}){return i(this,"_parse",(e,s)=>{let m=e(s);return n(m.data)?m:{success:false,error:{message:t}}}),this}};return f.length>0?f.reduce((n,t)=>t(n,r,o)??n,c):c}var f=[];function T(r){f.push(r);}exports.extend=T;exports.s=g;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- var y={object(n){let u=i(c=>typeof c=="object"&&c!==null&&!Array.isArray(c),{type:"object"});return s(u,"parse",(c,...o)=>{let e=c(...o);return n&&typeof e=="object"?Object.keys(n).reduce((r,t)=>{if(typeof n[t]?.parse=="function")try{r[t]=n[t].parse(e[t]);}catch(p){throw t=p.cause?.key?`${t}.${p.cause.key}`:t,new Error(`Error parsing key "${t}": ${p.message}`,{cause:{key:t}})}return r},{}):e}),u},string(){return i(a=>typeof a=="string",{type:"string"})},number(){return i(a=>typeof a=="number",{type:"number"})},boolean(){return i(a=>a===true||a===false,{type:"boolean"})},date(){return i(a=>a instanceof Date&&!Number.isNaN(a.getTime()),{type:"date"})},enum(n){let a=e=>n.includes(e),u=e=>`Invalid ${c} value. Expected ${n.map(r=>`"${r}"`).join(" | ")}, received "${e}".`,c="enum";return i(a,{type:c,message:u})},array(n){let u=i(c=>Array.isArray(c),{type:"array"});return s(u,"parse",(c,o)=>(o=c(o),n&&Array.isArray(o)?o.map((e,r)=>{try{return n.parse(e)}catch(t){let p=t.cause?.key?`${r}.${t.cause.key}`:r;throw new Error(`Error parsing index "${r}": ${t.message}`,{cause:{key:p}})}}):o)),u},any(){return i(()=>true)},preprocess(n,a){return s(a,"parse",(u,c)=>(c=n(c),u(c))),a}};function h(n){return a=>`The value "${a}" must be type of ${n} but is type of "${typeof a}".`}function s(n,a,u){let c=n[a];n[a]=(...o)=>u(c,...o);}function i(n,{type:a="any",message:u}={}){u=u||h(a);let c={message:u,type:a},o={parse(e){if(!n(e))throw new Error(u(e));return e},safeParse(e){try{return {success:!0,data:this.parse(e)}}catch(r){return {success:false,error:r}}},transform(e){return s(this,"parse",(r,t)=>e(r(t))),this},optional(){return s(this,"parse",(e,r)=>{try{return e(r)}catch{return}}),this},nullable(){return s(this,"parse",(e,r)=>{try{return e(r)}catch{return null}}),this},nullish(){return s(this,"parse",(e,r)=>{try{return e(r)}catch{return r==null?r:null}}),this},default(e){return s(this,"parse",(r,t)=>(t===void 0&&(t=e,t=typeof e=="function"?e():e),t=r(t),t)),this},pipe(e){return s(this,"parse",(r,t)=>e.parse(r(t))),this},refine(e,{message:r}){return s(this,"parse",(t,p)=>{let f=t(p);if(!e(f))throw new Error(r);return f}),this}};return m.reduce((e,r)=>r(e,n,c)??e,o)}var m=[];function I(n){m.push(n);}export{I as extend,y as s};
1
+ var h=r=>typeof r=="string"||r instanceof String,I=r=>typeof r=="number"||r instanceof Number,l=r=>r===true||r===false,y=r=>r instanceof Date&&!Number.isNaN(r.getTime()),d=r=>Array.isArray(r),S=r=>typeof r=="object"&&r!==null&&!Array.isArray(r),g={object(r){let a=p(S,{type:"object"});return i(a,"_parse",(u,...o)=>{let c=u(...o);if(c.success===false)return c;let n={};for(let t in r){let e=r[t]._parse(c.data[t]);if(e.success)n[t]=e.data;else {e=e;let s=e?.error?.cause?.key?`${t}.${e?.error?.cause?.key}`:t;return e.error.message=`Error parsing key "${s}": ${e.error.message}`,e.error.cause={key:s},e}}return {success:true,data:n}}),a},string(){return p(h,{type:"string"})},number(){return p(I,{type:"number"})},boolean(){return p(l,{type:"boolean"})},date(){return p(y,{type:"date"})},enum(r){let a=n=>r.includes(n),u=n=>`Invalid ${o} value. Expected ${r.map(t=>`"${t}"`).join(" | ")}, received "${n}".`,o="enum";return p(a,{type:o,message:u})},array(r){let a=p(d,{type:"array"});return i(a,"_parse",(u,...o)=>{let c=u(...o);if(c.success===false)return c;let n=[];for(let t=0;t<c.data.length;t++){let e=r._parse(c.data[t]);if(e.success)n.push(e.data);else {e=e;let s=e.error?.cause?.key?`${t}.${e.error.cause.key}`:`${t}`;return e.error.message=`Error parsing index "${t}": ${e.error.message}`,e.error.cause={key:s},e}}return {success:true,data:n}}),a},any(){return p(()=>true)},preprocess(r,a){return i(a,"_parse",(u,o)=>(o=r(o),u(o))),a}};function b(r){return a=>`The value "${a}" must be type of ${r} but is type of "${typeof a}".`}function i(r,a,u){let o=r[a];r[a]=(...c)=>u(o,...c);}function p(r,{type:a="any",message:u}={}){u=u||b(a);let o={message:u,type:a},c={_parse(n){return r(n)?{success:true,data:n}:{success:false,error:{message:u(n)}}},parse(n){let t=c._parse(n);if(!t.success)throw t=t,new Error(t.error.message,{cause:t.error.cause});return t.data},safeParse(n){return c._parse(n)},transform(n){return i(this,"_parse",(t,e)=>{let s=t(e);return s.success&&(s.data=n(s.data)),s}),this},optional(){return i(this,"_parse",(n,t)=>{let e=n(t);return e.success||(e.data=void 0,e.success=true),e}),this},nullable(){return i(this,"_parse",(n,t)=>{let e=n(t);return e.success||(e.data=null,e.success=true),e}),this},nullish(){return i(this,"_parse",(n,t)=>{let e=n(t);return !e.success&&t==null&&(e.success=true,e.data=t),e}),this},default(n){return i(this,"_parse",(t,e)=>(e===void 0&&(e=n,e=typeof n=="function"?n():n),t(e))),this},pipe(n){return i(this,"_parse",(t,e)=>{let s=t(e);return s.success?n._parse(s.data):s}),this},refine(n,{message:t}){return i(this,"_parse",(e,s)=>{let m=e(s);return n(m.data)?m:{success:false,error:{message:t}}}),this}};return f.length>0?f.reduce((n,t)=>t(n,r,o)??n,c):c}var f=[];function T(r){f.push(r);}export{T as extend,g as s};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@esmj/schema",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Tiny extendable package for schema validation.",
5
5
  "keywords": [
6
6
  "schema",
@@ -22,6 +22,7 @@
22
22
  "scripts": {
23
23
  "lint": "biome check --no-errors-on-unmatched",
24
24
  "lint:fix": "npm run lint -- --fix --unsafe",
25
+ "benchmark": "node benchmark/benchmark.ts",
25
26
  "dev": "node_modules/.bin/tsup --dts --watch --onSuccess 'node ./dist/index.mjs'",
26
27
  "test": "node --test --experimental-strip-types",
27
28
  "test:watch": "npm run test -- --watch",
@@ -62,17 +63,22 @@
62
63
  "homepage": "https://github.com/mjancarik/esmj-schema#readme",
63
64
  "devDependencies": {
64
65
  "@biomejs/biome": "1.9.4",
65
- "@commitlint/cli": "^19.8.0",
66
- "@commitlint/config-conventional": "^19.8.0",
67
- "@typescript-eslint/eslint-plugin": "^8.31.0",
68
- "@typescript-eslint/parser": "^8.31.0",
66
+ "@commitlint/cli": "^19.8.1",
67
+ "@commitlint/config-conventional": "^19.8.1",
68
+ "@typescript-eslint/eslint-plugin": "^8.33.0",
69
+ "@typescript-eslint/parser": "^8.33.0",
70
+ "@zod/mini": "^4.0.0-beta.0",
69
71
  "commitizen": "^4.3.1",
70
72
  "conventional-changelog-cli": "^5.0.0",
71
73
  "cz-conventional-changelog": "^3.3.0",
72
74
  "git-cz": "^4.9.0",
73
75
  "husky": "^9.1.7",
74
- "lint-staged": "^15.5.1",
76
+ "joi": "^17.13.3",
77
+ "lint-staged": "^16.1.0",
75
78
  "prettier": "^3.5.3",
76
- "tsup": "^8.4.0"
79
+ "superstruct": "^2.0.2",
80
+ "tsup": "^8.5.0",
81
+ "yup": "^1.6.1",
82
+ "zod": "^3.25.42"
77
83
  }
78
84
  }