From fa75147e4ebdba6fc6962fbedfeabd4dcdf2c3a7 Mon Sep 17 00:00:00 2001 From: Enzo Marioni Date: Sat, 18 Apr 2026 19:33:19 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20implement=20core=20Cereale?= =?UTF-8?q?=20library=20for=20JSON=20mapping=20and=20validation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ๐ŸŽจ add Spring-like decorators (@JsonSerialize, @JsonDeserialize, etc.) - โš™๏ธ implement JsonMapper and metadata storage for transformations - ๐Ÿงช add comprehensive test suite using Vitest - ๐Ÿ“ add README, CONTRIBUTING, and API documentation - ๐Ÿ‘ท setup GitHub Actions CI workflow - ๐Ÿ”ง configure TypeScript and project settings --- .gitignore | 3 + CONTRIBUTING.md | 10 +- README.md | 80 +++++- docs/{optimus.js => cereale.js} | 2 +- docs/index.html | 18 +- eslint.config.mjs | 29 ++ package.json | 36 ++- src/decorators.test.ts | 382 ++++++++++++++++++++++++++ src/decorators.ts | 26 +- src/example.ts | 13 +- src/index.test.ts | 3 +- src/index.ts | 6 +- src/interfaces.ts | 8 +- src/utils.test.ts | 93 +++++++ src/utils.ts | 457 +++++++++++++++++--------------- tsconfig.cjs.json | 9 + tsconfig.esm.json | 8 + tsconfig.json | 6 +- 18 files changed, 909 insertions(+), 280 deletions(-) rename docs/{optimus.js => cereale.js} (93%) create mode 100644 eslint.config.mjs create mode 100644 src/decorators.test.ts create mode 100644 src/utils.test.ts create mode 100644 tsconfig.cjs.json create mode 100644 tsconfig.esm.json diff --git a/.gitignore b/.gitignore index a98d6e5..9fe6ced 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ yarn-error.log* # Coverage /coverage/ + +# ESLint +.eslintcache diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f8996a7..73b6524 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ -# Contributing to Optimus +# Contributing to Cereale -Thank you for your interest in contributing to Optimus! We welcome all contributions, from bug reports to new features and documentation improvements. +Thank you for your interest in contributing to Cereale! We welcome all contributions, from bug reports to new features and documentation improvements. ## Development Setup @@ -8,8 +8,8 @@ To get started with development, follow these steps: 1. **Clone the repository:** ```bash - git clone https://github.com/Avalon-Vanguard/optimus.git - cd optimus + git clone https://github.com/Avalon-Vanguard/cereale.git + cd cereale ``` 2. **Install dependencies:** @@ -56,4 +56,4 @@ Please use GitHub Issues to report bugs or suggest new features. Provide as much --- -By contributing to this project, you agree to abide by the terms of the ISC License. +By contributing to this project, you agree to abide by the terms of the MIT License. diff --git a/README.md b/README.md index 53df51e..8220fae 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Optimus +# Cereale -Optimus is a lightweight TypeScript library that provides Spring-like decorators for JSON mapping and validation. Built with ZERO external dependencies, it simplifies the process of converting between plain JSON and class instances with full validation support. +Cereale is a lightweight TypeScript library that provides Spring-like decorators for JSON mapping and validation. Built with ZERO external dependencies, it simplifies the process of converting between plain JSON and class instances with full validation support. ## Features @@ -14,7 +14,7 @@ Optimus is a lightweight TypeScript library that provides Spring-like decorators ## Installation ```bash -npm install optimus +npm install cereale ``` Make sure to enable `experimentalDecorators` and `emitDecoratorMetadata` in your `tsconfig.json`: @@ -24,7 +24,7 @@ Make sure to enable `experimentalDecorators` and `emitDecoratorMetadata` in your "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true, - "target": "ES6" + "target": "ES2025" } } ``` @@ -47,7 +47,7 @@ import { JsonPolymorphic, JsonSerializer, JsonDeserializer -} from 'optimus'; +} from 'cereale'; // Custom Date Serializer class DateSerializer implements JsonSerializer { @@ -96,22 +96,22 @@ class Library { ### 2. Map JSON with Validation -Use `JsonMapper` to handle the conversion process. +Use standalone utility functions to handle the conversion process directly. ```typescript -import { JsonMapper, JsonValidationError } from 'optimus'; +import { fromJson, toJson, JsonValidationError } from 'cereale'; async function main() { const json = '{"name": "Central Library", "items": [{"type": "book", "title": "The Great Gatsby", "author": "F. Scott Fitzgerald", "publishedAt": "1925-04-10"}]}'; try { // Deserialize JSON to Class Instance - const library = await JsonMapper.fromJson(Library, json); + const library = await fromJson(Library, json); console.log(library.name); // "Central Library" console.log(library.items[0] instanceof Book); // true // Serialize Class Instance back to JSON - const outputJson = await JsonMapper.toJson(library); + const outputJson = await toJson(library); console.log(outputJson); } catch (error) { if (error instanceof JsonValidationError) { @@ -121,6 +121,20 @@ async function main() { } ``` +### 3. Modern Web Frameworks (Request Integration) + +Cereale is compatible with Fetch-based frameworks like Hono, Next.js, and Remix. Use the `fromRequest` async helper. + +```typescript +import { fromRequest, toPlain } from 'cereale'; + +// Hono Example +app.post('/books', async (c) => { + const book = await fromRequest(Book, c.req.raw); + return c.json(await toPlain(book)); +}); +``` + ## API Reference ### Decorators @@ -166,10 +180,48 @@ Most validation decorators accept an optional `ValidationOptions` object: ### Utilities -- `JsonMapper.toJson(obj: any, options?)`: Validates and serializes an instance to a JSON string. -- `JsonMapper.toPlain(obj: any, options?)`: Validates and transforms an instance to a plain object. -- `JsonMapper.fromJson(clazz: ClassConstructor, json: string, options?)`: Parses JSON and transforms it to a validated class instance. -- `JsonMapper.toInstance(clazz: ClassConstructor, plain: any, options?)`: Transforms a plain object to a validated class instance. +- `toJson(obj: any)`: Validates and serializes an instance to a JSON string (Returns `Promise`). +- `toPlain(obj: any)`: Validates and transforms an instance to a plain object (Returns `Promise`). +- `fromJson(clazz: ClassConstructor, json: string)`: Parses JSON and transforms it to a validated class instance (Returns `Promise`). +- `toInstance(clazz: ClassConstructor, plain: any)`: Transforms a plain object to a validated class instance (Returns `Promise`). +- `fromRequest(clazz: ClassConstructor, request: Request)`: Extracts JSON from a Fetch `Request` and transforms it to a validated instance (Returns `Promise`). +- `validate(obj: any)`: Performs full validation on an object/instance (Returns `Promise`). + +## Framework Integrations + +Cereale is designed to be compatible with all trending web frameworks. + +### Hono / Next.js / Cloudflare Workers +Use `fromRequest` for seamless integration with the Fetch `Request` API. + +### NestJS +You can use Cereale inside your controllers for explicit mapping and validation without needing `reflect-metadata`. + +```typescript +import { toInstance } from 'cereale'; + +@Post() +async create(@Body() body: any) { + const user = await toInstance(User, body); + return this.userService.create(user); +} +``` + +### Express / Fastify +Easily integrate with traditional Node.js frameworks. + +```typescript +import { toInstance, toPlain } from 'cereale'; + +app.post('/user', async (req, res) => { + try { + const user = await toInstance(User, req.body); + res.json(await toPlain(user)); + } catch (err) { + res.status(400).json(err); + } +}); +``` ## Contributing @@ -177,4 +229,4 @@ Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute t ## License -Optimus is licensed under the [ISC License](LICENSE). \ No newline at end of file +Cereale is licensed under the [MIT License](LICENSE). \ No newline at end of file diff --git a/docs/optimus.js b/docs/cereale.js similarity index 93% rename from docs/optimus.js rename to docs/cereale.js index 139fe69..e16a915 100644 --- a/docs/optimus.js +++ b/docs/cereale.js @@ -1 +1 @@ -"use strict";var Optimus=(()=>{var A=Object.defineProperty;var O=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames;var b=Object.prototype.hasOwnProperty;var $=(n,t)=>{for(var e in t)A(n,e,{get:t[e],enumerable:!0})},P=(n,t,e,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of T(t))!b.call(n,i)&&i!==e&&A(n,i,{get:()=>t[i],enumerable:!(a=O(t,i))||a.enumerable});return n};var S=n=>P(A({},"__esModule",{value:!0}),n);var it={};$(it,{ArrayMaxSize:()=>K,ArrayMinSize:()=>j,ArrayNotEmpty:()=>tt,Email:()=>_,IsArray:()=>X,IsBoolean:()=>R,IsDate:()=>at,IsDefined:()=>Y,IsIn:()=>et,IsInt:()=>k,IsNotEmpty:()=>Z,IsNotIn:()=>nt,IsNumber:()=>D,IsObject:()=>J,IsOptional:()=>z,IsString:()=>L,IsUrl:()=>G,JsonDeserialize:()=>w,JsonMapper:()=>x,JsonPolymorphic:()=>N,JsonSerialize:()=>V,JsonType:()=>C,JsonValidationError:()=>h,METADATA_KEYS:()=>u,Matches:()=>Q,Max:()=>H,MaxLength:()=>F,Min:()=>U,MinLength:()=>B,Negative:()=>q,Positive:()=>W,ValidateNested:()=>st});var M=class n{constructor(){this.properties=new WeakMap;this.propertyMetadata=new WeakMap;this.classMetadata=new WeakMap}static getInstance(){return n.instance||(n.instance=new n),n.instance}defineMetadata(t,e,a,i){if(i){let s=this.propertyMetadata.get(a);s||(s=new Map,this.propertyMetadata.set(a,s));let r=s.get(i);r||(r=new Map,s.set(i,r)),r.set(t,e)}else{let s=this.classMetadata.get(a);s||(s=new Map,this.classMetadata.set(a,s)),s.set(t,e)}}getMetadata(t,e,a){let i=e;for(;i;){let s=this.getOwnMetadata(t,i,a);if(s!==void 0)return s;i=Object.getPrototypeOf(i)}}getOwnMetadata(t,e,a){return a?this.propertyMetadata.get(e)?.get(a)?.get(t):this.classMetadata.get(e)?.get(t)}registerProperty(t,e){let a=this.properties.get(t);a||(a=[],this.properties.set(t,a)),a.includes(e)||a.push(e)}getProperties(t){let e=new Set,a=t;for(;a;){let i=this.properties.get(a);i&&i.forEach(s=>e.add(s)),a=Object.getPrototypeOf(a)}return Array.from(e)}},l=M.getInstance();var u={PROPERTIES:"optimus:properties",TYPE:"optimus:type",VALIDATION:"optimus:validation",SERIALIZER:"optimus:serializer",DESERIALIZER:"optimus:deserializer",POLYMORPHIC:"optimus:polymorphic",IS_OPTIONAL:"optimus:optional"};function f(n,t){l.registerProperty(n,t)}function o(n,t,e,a){f(n,t),a?.message&&(e.message=a.message);let i=l.getOwnMetadata(u.VALIDATION,n,t)||[];i.push(e),l.defineMetadata(u.VALIDATION,i,n,t)}function V(n){return(t,e)=>{f(t,e),l.defineMetadata(u.SERIALIZER,n,t,e)}}function w(n){return(t,e)=>{f(t,e),l.defineMetadata(u.DESERIALIZER,n,t,e)}}function C(n){return(t,e)=>{f(t,e),l.defineMetadata(u.TYPE,n,t,e)}}function N(n,t){return(e,a)=>{f(e,a),l.defineMetadata(u.POLYMORPHIC,{discriminator:n,subTypes:t},e,a)}}function z(){return(n,t)=>{f(n,t),l.defineMetadata(u.IS_OPTIONAL,!0,n,t)}}function L(){return(n,t)=>{o(n,t,{name:"isString",validate:e=>typeof e=="string",message:`${t} must be a string`})}}function R(){return(n,t)=>{o(n,t,{name:"isBoolean",validate:e=>typeof e=="boolean",message:`${t} must be a boolean`})}}function D(){return(n,t)=>{o(n,t,{name:"isNumber",validate:e=>typeof e=="number"&&!isNaN(e),message:`${t} must be a number`})}}function k(){return(n,t)=>{o(n,t,{name:"isInt",validate:e=>Number.isInteger(e),message:`${t} must be an integer`})}}function J(){return(n,t)=>{o(n,t,{name:"isObject",validate:e=>typeof e=="object"&&e!==null&&!Array.isArray(e),message:`${t} must be an object`})}}function Y(){return(n,t)=>{o(n,t,{name:"isDefined",validate:e=>e!=null,message:`${t} should not be null or undefined`})}}function Z(){return(n,t)=>{o(n,t,{name:"isNotEmpty",validate:e=>e!=null&&e!=="",message:`${t} should not be empty`})}}function U(n){return(t,e)=>{o(t,e,{name:"min",validate:a=>typeof a=="number"&&a>=n,message:`${e} must be at least ${n}`})}}function H(n){return(t,e)=>{o(t,e,{name:"max",validate:a=>typeof a=="number"&&a<=n,message:`${e} must be at most ${n}`})}}function W(){return(n,t)=>{o(n,t,{name:"positive",validate:e=>typeof e=="number"&&e>0,message:`${t} must be positive`})}}function q(){return(n,t)=>{o(n,t,{name:"negative",validate:e=>typeof e=="number"&&e<0,message:`${t} must be negative`})}}function B(n){return(t,e)=>{o(t,e,{name:"minLength",validate:a=>typeof a=="string"&&a.length>=n,message:`${e} must be longer than or equal to ${n} characters`})}}function F(n){return(t,e)=>{o(t,e,{name:"maxLength",validate:a=>typeof a=="string"&&a.length<=n,message:`${e} must be shorter than or equal to ${n} characters`})}}function _(){let n=/^[^\s@]+@[^\s@]+\.[^\s@]+$/;return(t,e)=>{o(t,e,{name:"isEmail",validate:a=>typeof a=="string"&&n.test(a),message:`${e} must be a valid email`})}}function G(){return(n,t)=>{o(n,t,{name:"isUrl",validate:e=>{try{return new URL(e),!0}catch{return!1}},message:`${t} must be a valid URL`})}}function Q(n){return(t,e)=>{o(t,e,{name:"matches",validate:a=>typeof a=="string"&&n.test(a),message:`${e} must match ${n} regular expression`})}}function X(){return(n,t)=>{o(n,t,{name:"isArray",validate:e=>Array.isArray(e),message:`${t} must be an array`})}}function j(n){return(t,e)=>{o(t,e,{name:"arrayMinSize",validate:a=>Array.isArray(a)&&a.length>=n,message:`${e} must contain at least ${n} elements`})}}function K(n){return(t,e)=>{o(t,e,{name:"arrayMaxSize",validate:a=>Array.isArray(a)&&a.length<=n,message:`${e} must contain at most ${n} elements`})}}function tt(){return(n,t)=>{o(n,t,{name:"arrayNotEmpty",validate:e=>Array.isArray(e)&&e.length>0,message:`${t} should not be empty`})}}function et(n){return(t,e)=>{o(t,e,{name:"isIn",validate:a=>n.includes(a),message:`${e} must be one of the following values: ${n.join(", ")}`})}}function nt(n){return(t,e)=>{o(t,e,{name:"isNotIn",validate:a=>!n.includes(a),message:`${e} must not be one of the following values: ${n.join(", ")}`})}}function at(){return(n,t)=>{o(n,t,{name:"isDate",validate:e=>e instanceof Date&&!isNaN(e.getTime()),message:`${t} must be a valid Date object`})}}function st(){return(n,t)=>{f(n,t),l.defineMetadata("optimus:nested",!0,n,t)}}var h=class extends Error{constructor(e,a){super(e);this.errors=a;this.name="JsonValidationError"}toString(){return`${this.message}: ${JSON.stringify(this.errors,null,2)}`}},x=class{static async toPlain(t){if(t==null)return t;let e=await this.validate(t);if(e.length>0)throw new h("Validation failed during serialization",e);return this.serialize(t)}static async toJson(t){let e=await this.toPlain(t);return JSON.stringify(e)}static async toInstance(t,e){let a=this.deserialize(t,e),i=await this.validate(a);if(i.length>0)throw new h("Validation failed during deserialization",i);return a}static async fromJson(t,e){let a=JSON.parse(e);return this.toInstance(t,a)}static serialize(t){if(t==null||typeof t!="object")return t;if(Array.isArray(t))return t.map(s=>this.serialize(s));if(t instanceof Date)return t.toISOString();let e=t.constructor.prototype,a={},i=Object.keys(t);for(let s of i){let r=t[s],g=l.getMetadata(u.SERIALIZER,e,s);if(g){let p=new g;a[s]=p.serialize(r)}else a[s]=this.serialize(r)}return a}static deserialize(t,e){if(e==null)return e;if(Array.isArray(e))return e.map(s=>this.deserialize(t,s));let a=new t,i=t.prototype;for(let s of Object.keys(e)){let r=e[s],g=l.getMetadata(u.DESERIALIZER,i,s);if(g){let d=new g;a[s]=d.deserialize(r);continue}let p=l.getMetadata(u.POLYMORPHIC,i,s);if(p&&r!==null&&r!==void 0){let{discriminator:d,subTypes:y}=p;if(Array.isArray(r))a[s]=r.map(m=>{let c=y.find(v=>m[d]===v.name);return c?this.deserialize(c.value,m):m});else{let m=y.find(c=>r[d]===c.name);if(m){a[s]=this.deserialize(m.value,r);continue}}continue}let I=l.getMetadata(u.TYPE,i,s);if(I&&r!==null&&r!==void 0){let d=I();a[s]=this.deserialize(d,r);continue}a[s]=r}return a}static async validate(t){let e=[];if(t==null||typeof t!="object")return e;if(Array.isArray(t)){for(let s=0;s0&&e.push({property:`[${s}]`,value:t[s],constraints:{},children:r})}return e}let a=Object.getPrototypeOf(t),i=l.getProperties(a);for(let s of i){let r=t[s],g={property:s,value:r,constraints:{}},p=l.getMetadata(u.IS_OPTIONAL,a,s),I=r==null;if(p&&I)continue;let d=l.getMetadata(u.VALIDATION,a,s)||[],y={value:r,object:t,property:s,constraints:[]};for(let c of d)if(y.constraints=c.constraints||[],!await c.validate(r,y)){let E=typeof c.message=="function"?c.message(y):c.message;g.constraints[c.name]=E}if(l.getMetadata("optimus:nested",a,s)&&r!==null&&r!==void 0){let c=await this.validate(r);c.length>0&&(g.children=c)}(Object.keys(g.constraints).length>0||g.children)&&e.push(g)}return e}};return S(it);})(); +"use strict";var Cereale=(()=>{var A=Object.defineProperty;var O=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames;var b=Object.prototype.hasOwnProperty;var $=(n,t)=>{for(var e in t)A(n,e,{get:t[e],enumerable:!0})},P=(n,t,e,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of T(t))!b.call(n,i)&&i!==e&&A(n,i,{get:()=>t[i],enumerable:!(a=O(t,i))||a.enumerable});return n};var S=n=>P(A({},"__esModule",{value:!0}),n);var it={};$(it,{ArrayMaxSize:()=>K,ArrayMinSize:()=>j,ArrayNotEmpty:()=>tt,Email:()=>_,IsArray:()=>X,IsBoolean:()=>R,IsDate:()=>at,IsDefined:()=>Y,IsIn:()=>et,IsInt:()=>k,IsNotEmpty:()=>Z,IsNotIn:()=>nt,IsNumber:()=>D,IsObject:()=>J,IsOptional:()=>z,IsString:()=>L,IsUrl:()=>G,JsonDeserialize:()=>w,JsonMapper:()=>x,JsonPolymorphic:()=>N,JsonSerialize:()=>V,JsonType:()=>C,JsonValidationError:()=>h,METADATA_KEYS:()=>u,Matches:()=>Q,Max:()=>H,MaxLength:()=>F,Min:()=>U,MinLength:()=>B,Negative:()=>q,Positive:()=>W,ValidateNested:()=>st});var M=class n{constructor(){this.properties=new WeakMap;this.propertyMetadata=new WeakMap;this.classMetadata=new WeakMap}static getInstance(){return n.instance||(n.instance=new n),n.instance}defineMetadata(t,e,a,i){if(i){let s=this.propertyMetadata.get(a);s||(s=new Map,this.propertyMetadata.set(a,s));let r=s.get(i);r||(r=new Map,s.set(i,r)),r.set(t,e)}else{let s=this.classMetadata.get(a);s||(s=new Map,this.classMetadata.set(a,s)),s.set(t,e)}}getMetadata(t,e,a){let i=e;for(;i;){let s=this.getOwnMetadata(t,i,a);if(s!==void 0)return s;i=Object.getPrototypeOf(i)}}getOwnMetadata(t,e,a){return a?this.propertyMetadata.get(e)?.get(a)?.get(t):this.classMetadata.get(e)?.get(t)}registerProperty(t,e){let a=this.properties.get(t);a||(a=[],this.properties.set(t,a)),a.includes(e)||a.push(e)}getProperties(t){let e=new Set,a=t;for(;a;){let i=this.properties.get(a);i&&i.forEach(s=>e.add(s)),a=Object.getPrototypeOf(a)}return Array.from(e)}},l=M.getInstance();var u={PROPERTIES:"cereale:properties",TYPE:"cereale:type",VALIDATION:"cereale:validation",SERIALIZER:"cereale:serializer",DESERIALIZER:"cereale:deserializer",POLYMORPHIC:"cereale:polymorphic",IS_OPTIONAL:"cereale:optional"};function f(n,t){l.registerProperty(n,t)}function o(n,t,e,a){f(n,t),a?.message&&(e.message=a.message);let i=l.getOwnMetadata(u.VALIDATION,n,t)||[];i.push(e),l.defineMetadata(u.VALIDATION,i,n,t)}function V(n){return(t,e)=>{f(t,e),l.defineMetadata(u.SERIALIZER,n,t,e)}}function w(n){return(t,e)=>{f(t,e),l.defineMetadata(u.DESERIALIZER,n,t,e)}}function C(n){return(t,e)=>{f(t,e),l.defineMetadata(u.TYPE,n,t,e)}}function N(n,t){return(e,a)=>{f(e,a),l.defineMetadata(u.POLYMORPHIC,{discriminator:n,subTypes:t},e,a)}}function z(){return(n,t)=>{f(n,t),l.defineMetadata(u.IS_OPTIONAL,!0,n,t)}}function L(){return(n,t)=>{o(n,t,{name:"isString",validate:e=>typeof e=="string",message:`${t} must be a string`})}}function R(){return(n,t)=>{o(n,t,{name:"isBoolean",validate:e=>typeof e=="boolean",message:`${t} must be a boolean`})}}function D(){return(n,t)=>{o(n,t,{name:"isNumber",validate:e=>typeof e=="number"&&!isNaN(e),message:`${t} must be a number`})}}function k(){return(n,t)=>{o(n,t,{name:"isInt",validate:e=>Number.isInteger(e),message:`${t} must be an integer`})}}function J(){return(n,t)=>{o(n,t,{name:"isObject",validate:e=>typeof e=="object"&&e!==null&&!Array.isArray(e),message:`${t} must be an object`})}}function Y(){return(n,t)=>{o(n,t,{name:"isDefined",validate:e=>e!=null,message:`${t} should not be null or undefined`})}}function Z(){return(n,t)=>{o(n,t,{name:"isNotEmpty",validate:e=>e!=null&&e!=="",message:`${t} should not be empty`})}}function U(n){return(t,e)=>{o(t,e,{name:"min",validate:a=>typeof a=="number"&&a>=n,message:`${e} must be at least ${n}`})}}function H(n){return(t,e)=>{o(t,e,{name:"max",validate:a=>typeof a=="number"&&a<=n,message:`${e} must be at most ${n}`})}}function W(){return(n,t)=>{o(n,t,{name:"positive",validate:e=>typeof e=="number"&&e>0,message:`${t} must be positive`})}}function q(){return(n,t)=>{o(n,t,{name:"negative",validate:e=>typeof e=="number"&&e<0,message:`${t} must be negative`})}}function B(n){return(t,e)=>{o(t,e,{name:"minLength",validate:a=>typeof a=="string"&&a.length>=n,message:`${e} must be longer than or equal to ${n} characters`})}}function F(n){return(t,e)=>{o(t,e,{name:"maxLength",validate:a=>typeof a=="string"&&a.length<=n,message:`${e} must be shorter than or equal to ${n} characters`})}}function _(){let n=/^[^\s@]+@[^\s@]+\.[^\s@]+$/;return(t,e)=>{o(t,e,{name:"isEmail",validate:a=>typeof a=="string"&&n.test(a),message:`${e} must be a valid email`})}}function G(){return(n,t)=>{o(n,t,{name:"isUrl",validate:e=>{try{return new URL(e),!0}catch{return!1}},message:`${t} must be a valid URL`})}}function Q(n){return(t,e)=>{o(t,e,{name:"matches",validate:a=>typeof a=="string"&&n.test(a),message:`${e} must match ${n} regular expression`})}}function X(){return(n,t)=>{o(n,t,{name:"isArray",validate:e=>Array.isArray(e),message:`${t} must be an array`})}}function j(n){return(t,e)=>{o(t,e,{name:"arrayMinSize",validate:a=>Array.isArray(a)&&a.length>=n,message:`${e} must contain at least ${n} elements`})}}function K(n){return(t,e)=>{o(t,e,{name:"arrayMaxSize",validate:a=>Array.isArray(a)&&a.length<=n,message:`${e} must contain at most ${n} elements`})}}function tt(){return(n,t)=>{o(n,t,{name:"arrayNotEmpty",validate:e=>Array.isArray(e)&&e.length>0,message:`${t} should not be empty`})}}function et(n){return(t,e)=>{o(t,e,{name:"isIn",validate:a=>n.includes(a),message:`${e} must be one of the following values: ${n.join(", ")}`})}}function nt(n){return(t,e)=>{o(t,e,{name:"isNotIn",validate:a=>!n.includes(a),message:`${e} must not be one of the following values: ${n.join(", ")}`})}}function at(){return(n,t)=>{o(n,t,{name:"isDate",validate:e=>e instanceof Date&&!isNaN(e.getTime()),message:`${t} must be a valid Date object`})}}function st(){return(n,t)=>{f(n,t),l.defineMetadata("cereale:nested",!0,n,t)}}var h=class extends Error{constructor(e,a){super(e);this.errors=a;this.name="JsonValidationError"}toString(){return`${this.message}: ${JSON.stringify(this.errors,null,2)}`}},x=class{static async toPlain(t){if(t==null)return t;let e=await this.validate(t);if(e.length>0)throw new h("Validation failed during serialization",e);return this.serialize(t)}static async toJson(t){let e=await this.toPlain(t);return JSON.stringify(e)}static async toInstance(t,e){let a=this.deserialize(t,e),i=await this.validate(a);if(i.length>0)throw new h("Validation failed during deserialization",i);return a}static async fromJson(t,e){let a=JSON.parse(e);return this.toInstance(t,a)}static serialize(t){if(t==null||typeof t!="object")return t;if(Array.isArray(t))return t.map(s=>this.serialize(s));if(t instanceof Date)return t.toISOString();let e=t.constructor.prototype,a={},i=Object.keys(t);for(let s of i){let r=t[s],g=l.getMetadata(u.SERIALIZER,e,s);if(g){let p=new g;a[s]=p.serialize(r)}else a[s]=this.serialize(r)}return a}static deserialize(t,e){if(e==null)return e;if(Array.isArray(e))return e.map(s=>this.deserialize(t,s));let a=new t,i=t.prototype;for(let s of Object.keys(e)){let r=e[s],g=l.getMetadata(u.DESERIALIZER,i,s);if(g){let d=new g;a[s]=d.deserialize(r);continue}let p=l.getMetadata(u.POLYMORPHIC,i,s);if(p&&r!==null&&r!==void 0){let{discriminator:d,subTypes:y}=p;if(Array.isArray(r))a[s]=r.map(m=>{let c=y.find(v=>m[d]===v.name);return c?this.deserialize(c.value,m):m});else{let m=y.find(c=>r[d]===c.name);if(m){a[s]=this.deserialize(m.value,r);continue}}continue}let I=l.getMetadata(u.TYPE,i,s);if(I&&r!==null&&r!==void 0){let d=I();a[s]=this.deserialize(d,r);continue}a[s]=r}return a}static async validate(t){let e=[];if(t==null||typeof t!="object")return e;if(Array.isArray(t)){for(let s=0;s0&&e.push({property:`[${s}]`,value:t[s],constraints:{},children:r})}return e}let a=Object.getPrototypeOf(t),i=l.getProperties(a);for(let s of i){let r=t[s],g={property:s,value:r,constraints:{}},p=l.getMetadata(u.IS_OPTIONAL,a,s),I=r==null;if(p&&I)continue;let d=l.getMetadata(u.VALIDATION,a,s)||[],y={value:r,object:t,property:s,constraints:[]};for(let c of d)if(y.constraints=c.constraints||[],!await c.validate(r,y)){let E=typeof c.message=="function"?c.message(y):c.message;g.constraints[c.name]=E}if(l.getMetadata("cereale:nested",a,s)&&r!==null&&r!==void 0){let c=await this.validate(r);c.length>0&&(g.children=c)}(Object.keys(g.constraints).length>0||g.children)&&e.push(g)}return e}};return S(it);})(); diff --git a/docs/index.html b/docs/index.html index 0c315f6..2f83b6e 100644 --- a/docs/index.html +++ b/docs/index.html @@ -3,14 +3,14 @@ - Optimus - Spring-like JSON Mapping & Validation for TypeScript + Cereale - Spring-like JSON Mapping & Validation for TypeScript - +