@stacksjs/validation 0.75.109 → 0.75.110
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 +22 -0
- package/dist/conditional.js +1 -0
- package/dist/file-validator.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -2
- package/dist/input.d.ts +15 -0
- package/dist/input.js +1 -0
- package/dist/is.d.ts +29 -0
- package/dist/is.js +1 -0
- package/dist/object-with-context.js +1 -0
- package/dist/reporter.js +1 -0
- package/dist/request-validator.js +1 -2
- package/dist/rules.d.ts +30 -0
- package/dist/rules.js +0 -0
- package/dist/runtime.js +1 -2
- package/dist/schema.js +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.js +0 -0
- package/dist/validator.js +1 -0
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -51,3 +51,25 @@ For casual chit-chat with others using this package:
|
|
|
51
51
|
The MIT License (MIT). Please see [LICENSE](https://github.com/stacksjs/stacks/tree/main/LICENSE.md) for more information.
|
|
52
52
|
|
|
53
53
|
Made with 💙
|
|
54
|
+
|
|
55
|
+
## Custom action input
|
|
56
|
+
|
|
57
|
+
Use the narrow entry when a custom action validates JSON, route identifiers or
|
|
58
|
+
partial model fields:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import { readJsonObject, parsePositiveId, parseNumberInput, parseBooleanInput,
|
|
62
|
+
validateModelInput } from '@stacksjs/validation/input'
|
|
63
|
+
|
|
64
|
+
const body = await readJsonObject(request)
|
|
65
|
+
const id = parsePositiveId(request.params.id)
|
|
66
|
+
const amount = parseNumberInput(body.amount, 'amount', 0, 100000, true)
|
|
67
|
+
const enabled = parseBooleanInput(body.enabled, 'enabled')
|
|
68
|
+
validateModelInput(MyModel, { amount })
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Failures throw `InputValidationError` with HTTP `status: 422`. Numbers reject
|
|
72
|
+
booleans, non-finite values and unsafe integers. Optional text and numbers return
|
|
73
|
+
null; omitted booleans return undefined. Explicit false and zero are preserved.
|
|
74
|
+
Model validation checks supplied fields against their declared rules; the action
|
|
75
|
+
still selects writable fields and enforces required fields and relationships.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function when(field,match,refine){if(!this.__conditionals)this.__conditionals=[];this.__conditionals.push({field,match,refine});return this}function sometimes(){this.optional();return this}export function withConditionals(validator){const augmented=validator;augmented.when=when;augmented.sometimes=sometimes;return augmented}export function shouldApplyConditional(record,parent){if(parent===null||parent===void 0)return!1;const siblingValue=parent[record.field];if(typeof record.match==="function")return record.match(siblingValue);return siblingValue===record.match}export function applyConditionals(base,parent){const records=base.__conditionals;if(!records||records.length===0)return base;let working=null;for(const record of records){if(!shouldApplyConditional(record,parent))continue;if(working===null)working=cloneValidator(base);working=record.refine(working)}return working??base}function cloneValidator(v){const clone=Object.create(Object.getPrototypeOf(v));Object.assign(clone,v);const rules=v.rules;if(Array.isArray(rules))clone.rules=rules.slice();return clone}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const IMAGE_MIMETYPES=new Set(["image/jpeg","image/png","image/webp","image/gif","image/avif"]);function mimetypeOf(file){return file.mimetype??file.mimeType}function originalNameOf(file){return file.originalName??file.name}function extensionOf(file){const name=originalNameOf(file);if(!name)return null;const idx=name.lastIndexOf(".");if(idx<=0||idx===name.length-1)return null;const ext=name.slice(idx+1).toLowerCase();return/^[a-z0-9]+$/.test(ext)?ext:null}export class FileValidator{_required=!1;_rules=[];required(){this._required=!0;return this}image(){this._rules.push((file)=>{const mime=mimetypeOf(file);if(!mime||!IMAGE_MIMETYPES.has(mime.toLowerCase()))return`must be an image (got ${mime||"no mimetype"})`;return null});return this}mimeTypes(allowed){const normalized=new Set(allowed.map((m)=>m.toLowerCase()));this._rules.push((file)=>{const mime=mimetypeOf(file);if(!mime||!normalized.has(mime.toLowerCase()))return`must be one of: ${allowed.join(", ")} (got ${mime||"no mimetype"})`;return null});return this}maxBytes(max){this._rules.push((file)=>{if(typeof file.size!=="number")return"has no size - cannot enforce maxBytes";if(file.size>max)return`is too large: ${file.size} bytes exceeds the ${max}-byte cap`;return null});return this}minBytes(min){this._rules.push((file)=>{if(typeof file.size!=="number")return"has no size - cannot enforce minBytes";if(file.size<min)return`is too small: ${file.size} bytes is below the ${min}-byte minimum`;return null});return this}extensions(allowed){const normalized=new Set(allowed.map((e)=>e.toLowerCase().replace(/^\./,"")));this._rules.push((file)=>{const ext=extensionOf(file);if(!ext||!normalized.has(ext))return`must have one of these extensions: ${allowed.join(", ")} (got ${ext?`.${ext}`:"no extension"})`;return null});return this}custom(rule){this._rules.push(rule);return this}validate(value){if(value===null||value===void 0){if(this._required)return{valid:!1,errors:[{message:"is required"}]};return{valid:!0}}if(typeof value!=="object"||typeof value.size!=="number")return{valid:!1,errors:[{message:"must be an uploaded file"}]};const file=value,errors=[];for(const rule of this._rules){const msg=rule(file);if(msg)errors.push({message:msg})}return errors.length===0?{valid:!0}:{valid:!1,errors}}}export function file(){return new FileValidator}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import{v as x}from"@stacksjs/ts-validation";var M=new Set(["image/jpeg","image/png","image/webp","image/gif","image/avif"]);function j(e){return e.mimetype??e.mimeType}function F(e){return e.originalName??e.name}function N(e){let t=F(e);if(!t)return null;let n=t.lastIndexOf(".");if(n<=0||n===t.length-1)return null;let r=t.slice(n+1).toLowerCase();return/^[a-z0-9]+$/.test(r)?r:null}class T{_required=!1;_rules=[];required(){return this._required=!0,this}image(){return this._rules.push((e)=>{let t=j(e);if(!t||!M.has(t.toLowerCase()))return`must be an image (got ${t||"no mimetype"})`;return null}),this}mimeTypes(e){let t=new Set(e.map((n)=>n.toLowerCase()));return this._rules.push((n)=>{let r=j(n);if(!r||!t.has(r.toLowerCase()))return`must be one of: ${e.join(", ")} (got ${r||"no mimetype"})`;return null}),this}maxBytes(e){return this._rules.push((t)=>{if(typeof t.size!=="number")return"has no size - cannot enforce maxBytes";if(t.size>e)return`is too large: ${t.size} bytes exceeds the ${e}-byte cap`;return null}),this}minBytes(e){return this._rules.push((t)=>{if(typeof t.size!=="number")return"has no size - cannot enforce minBytes";if(t.size<e)return`is too small: ${t.size} bytes is below the ${e}-byte minimum`;return null}),this}extensions(e){let t=new Set(e.map((n)=>n.toLowerCase().replace(/^\./,"")));return this._rules.push((n)=>{let r=N(n);if(!r||!t.has(r))return`must have one of these extensions: ${e.join(", ")} (got ${r?`.${r}`:"no extension"})`;return null}),this}custom(e){return this._rules.push(e),this}validate(e){if(e===null||e===void 0){if(this._required)return{valid:!1,errors:[{message:"is required"}]};return{valid:!0}}if(typeof e!=="object"||typeof e.size!=="number")return{valid:!1,errors:[{message:"must be an uploaded file"}]};let t=e,n=[];for(let r of this._rules){let a=r(t);if(a)n.push({message:a})}return n.length===0?{valid:!0}:{valid:!1,errors:n}}}function R(){return new T}function W(e,t,n){if(!this.__conditionals)this.__conditionals=[];return this.__conditionals.push({field:e,match:t,refine:n}),this}function _(){return this.optional(),this}function f(e){let t=e;return t.when=W,t.sometimes=_,t}function w(e,t){if(t===null||t===void 0)return!1;let n=t[e.field];if(typeof e.match==="function")return e.match(n);return n===e.match}function h(e,t){let n=e.__conditionals;if(!n||n.length===0)return e;let r=null;for(let a of n){if(!w(a,t))continue;if(r===null)r=L(e);r=a.refine(r)}return r??e}function L(e){let t=Object.create(Object.getPrototypeOf(e));Object.assign(t,e);let n=e.rules;if(Array.isArray(n))t.rules=n.slice();return t}function p(e){let t=e??{},n=!0,r={name:"object",get isRequired(){return n},getRules:()=>[],test(i){return this.validate(i).valid},isPartOfShape:!1,shape(i){return t=i,r},getShape(){return t},required(){return n=!0,a},optional(){return n=!1,a},validate(i){let o=i===void 0||i===null;if(!n&&o)return{valid:!0,errors:this.isPartOfShape?{}:[]};if(n&&o)return this.isPartOfShape?{valid:!1,errors:{object:[{message:"value is required"}]}}:{valid:!1,errors:[{message:"value is required"}]};if(typeof i!=="object"||Array.isArray(i))return this.isPartOfShape?{valid:!1,errors:{object:[{message:"Must be an object"}]}}:{valid:!1,errors:[{message:"Must be an object"}]};let s=i,l={},u=!1;for(let d of Object.keys(t)){let c=t[d];if(!c)continue;let g=h(c,s),b=s[d],y=g.validate(b);if(y.valid)continue;if(u=!0,!Array.isArray(y.errors))for(let[E,q]of Object.entries(y.errors))l[`${d}.${E}`]=q;else l[d]=y.errors}if(u)return{valid:!1,errors:l};return{valid:!0,errors:{}}}},a=f(r);return a}var z=new Set(["array","bigint","binary","blob","boolean","custom","date","datetime","decimal","double","enum","float","integer","json","number","password","smallint","string","text","time","timestamp","timestampTz","unix"]);function B(e){if(e instanceof Date)return!Number.isNaN(e.getTime());if(typeof e!=="string"||!/^\d{4}-\d{2}-\d{2}$/.test(e))return!1;let t=new Date(`${e}T00:00:00.000Z`);return!Number.isNaN(t.getTime())&&t.toISOString().slice(0,10)===e}function D(){let e=x.custom(B,"Must be a valid date");return e.name="date",f(e)}var K=/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?$/;function U(e){if(e instanceof Date)return!Number.isNaN(e.getTime());if(typeof e==="number")return Number.isFinite(e)&&e>=0;if(typeof e!=="string")return!1;let t=e.trim();if(!t)return!1;if(/^\d+$/.test(t))return!0;if(K.test(t))return!Number.isNaN(new Date(t.replace(" ","T")).getTime());return!Number.isNaN(new Date(t).getTime())}function H(){let e=x.custom(U,"Must be a valid timestamp");return e.name="timestamp",f(e)}var C=Object.create(null),V=new Proxy(x,{get(e,t,n){if(t==="file")return R;if(t==="object")return p;if(t==="date")return D;if(t==="timestamp")return H;if(typeof t==="string"){let r=C[t];if(r)return r;if(!z.has(t))return Reflect.get(e,t,n);let a=(...i)=>{let o=Reflect.get(e,t,n);return f(o(...i))};return C[t]=a,a}return Reflect.get(e,t,n)}});export*from"@stacksjs/ts-validation";var O=[];function v(e){O=e}function xe(){return O}import{HttpError as m}from"@stacksjs/error-handling";import{path as A}from"@stacksjs/path";import{snakeCase as ne}from"@stacksjs/strings";import{getErrorMessage as S}from"@stacksjs/utils";import{MessageProvider as re,setCustomMessages as ie}from"@stacksjs/ts-validation";import{MessageProvider as Y,setCustomMessages as G}from"@stacksjs/ts-validation";import{HttpError as k}from"@stacksjs/error-handling/http-error";var Q=p();async function Z(e){if(!e||typeof e!=="object")return{};if(typeof e.all==="function"){let r=e.all();return r instanceof Promise?await r:r}let t=e,n={};if(t.url)try{new URL(t.url).searchParams.forEach((a,i)=>{n[i]=a})}catch{}if(t.query&&typeof t.query==="object")Object.assign(n,t.query);if(t.jsonBody&&typeof t.jsonBody==="object")Object.assign(n,t.jsonBody);if(t.formBody&&typeof t.formBody==="object")Object.assign(n,t.formBody);if(t.params&&typeof t.params==="object")Object.assign(n,t.params);if(Object.keys(n).length===0&&!("all"in e)&&!("jsonBody"in e))return e;return n}async function J(e,t){let n=await Z(e),r={},a;for(let[i,o]of Object.entries(t)){if(!o)continue;if(typeof o.rule==="object"||typeof o.rule==="function"){r[i]=o.rule;let s=o.message;if(typeof s==="string")(a??={})[`${i}.default`]=s;else if(s&&typeof s==="object"){a??={};for(let[l,u]of Object.entries(s))a[`${i}.${l}`]=u}}else r[i]=o}try{if(a)G(new Y(a));let i=await Q.shape(r).validate(n);if(!i.valid){let s=te(i.errors);throw new k(422,"Validation failed",{errors:s})}return i.value??n}catch(i){if(X(i))throw i;throw new k(500,ee(i)||"An unexpected validation error occurred")}}function X(e){return e instanceof k||e instanceof Error&&typeof e.status==="number"}function ee(e){if(e instanceof Error)return e.message;if(typeof e==="string")return e;if(typeof e==="number"||typeof e==="boolean")return String(e);if(e&&typeof e==="object"&&"message"in e&&typeof e.message==="string")return e.message;if(e==null)return"Unknown error";try{return String(e)}catch{return"Unknown error"}}function te(e){let t={};if(Array.isArray(e)){for(let n of e){if(!n||typeof n!=="object")continue;let r=typeof n.field==="string"?n.field:"_",a=typeof n.message==="string"?n.message:"Validation failed";(t[r]??=[]).push(a)}return t}if(!e||typeof e!=="object")return t;for(let[n,r]of Object.entries(e)){let a=Array.isArray(r)?r:[r];for(let i of a){let o=typeof i==="string"?i:i&&typeof i==="object"&&typeof i.message==="string"?i.message:"Validation failed";(t[n]??=[]).push(o)}}return t}function ae(e,t={}){let n=[];for(let r of e){let i=r.startsWith("/")?r.lastIndexOf("/"):-1,o=r.indexOf("*"),s=o===-1?i:r.lastIndexOf("/",o),l=s>0?r.slice(0,s):".",u=s>0?r.slice(s+1):r,d=new Bun.Glob(u);for(let c of d.scanSync({cwd:l,onlyFiles:!0}))n.push(t.absolute?`${l}/${c}`:c)}return n}function qe(e){if(e===void 0)return!1;return Object.keys(e).length>0}function oe(e,t){let n=(r)=>{let a=t[r];return a===void 0||a===null?"":String(a)};if(Array.isArray(e))return e.map((r)=>({message:r.message,value:"",field:""}));return Object.entries(e).flatMap(([r,a])=>a.map((i)=>({message:i.message,value:n(r),field:r})))}async function se(e,t){let r=ae([A.userModelsPath("*.ts"),A.storagePath("framework/defaults/app/Models/**/*.ts")],{absolute:!0}).find((l)=>l.endsWith(`${e}.ts`));if(!r)throw new m(404,`Model ${e} not found`);let i=(await import(r)).default.attributes,o={},s={};for(let l in i)if(Object.prototype.hasOwnProperty.call(i,l)){let u=i[l];if(!u)continue;let d="isRequired"in(u.validation?.rule??{})?(u.validation?.rule).isRequired:!1;if(u.default!==void 0&&d===!1)continue;o[ne(l)]=u.validation?.rule;let c=u.validation?.message;if(c)for(let g in c){let b=`${l}.${g}`;s[b]=c[g]||""}}try{ie(new re(s));let u=await V.object(o).validate(t);if(!u.valid)throw v(oe(u.errors,t)),new m(422,"Validation failed",{errors:u.errors});return u}catch(l){if(P(l))throw l;throw new m(500,S(l)||"An unexpected validation error occurred")}}var I=new Map;function Me(e,t){I.set(e,t)}function Fe(e){return I.get(e)}function Ne(e,t,n){return async(r)=>{try{let{db:a}=await import("@stacksjs/database/runtime"),i=a.selectFrom(e).where(t,"=",r);if(n)i=i.where("id","!=",n);return await i.selectAll().executeTakeFirst()?`The ${t} has already been taken`:!0}catch{return!0}}}function We(e,t){return async(n)=>{try{let{db:r}=await import("@stacksjs/database/runtime");return await r.selectFrom(e).where(t,"=",n).selectAll().executeTakeFirst()?!0:`The selected ${t} does not exist`}catch{return!0}}}async function $e(e,t,n){let r=await se(e,t);if(n&&Object.keys(n).length>0){let a={};if(await Promise.all(Object.entries(n).map(async([i,o])=>{let s=await o(t[i],t);if(s!==!0)a[i]=typeof s==="string"?s:`${i} validation failed`})),Object.keys(a).length>0)throw new m(422,"Validation failed",{errors:a})}return r}async function _e(e,t){let n={},r={};for(let a in e)if(Object.prototype.hasOwnProperty.call(e,a)){let i=e[a]?.rule;if(i)n[a]=i;let o=e[a]?.message;for(let s in o){let l=`${a}.${s}`;r[l]=e[a]?.message[s]||""}}try{let i=await V.object().shape(n).validate(t);if(!i.valid)throw new m(422,"Validation failed",{errors:i.errors});return i}catch(a){if(P(a))throw a;throw new m(500,S(a)||"An unexpected validation error occurred")}}function P(e){return e instanceof m||e instanceof Error&&typeof e.status==="number"}function Be(e){return typeof e==="string"}function De(e){return typeof e==="number"&&!Number.isNaN(e)}function Ke(e){return typeof e==="boolean"}function Ue(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function He(e){return Array.isArray(e)}function Ye(e){return typeof e==="function"}function Ge(e){return e===void 0}function Qe(e){return e===null}function Ze(e){return e===null||e===void 0}export{h as applyConditionals,_e as customValidate,We as exists,Fe as getCustomRule,xe as getErrors,He as isArray,Ke as isBoolean,Ye as isFunction,Qe as isNull,Ze as isNullOrUndefined,De as isNumber,Ue as isObject,qe as isObjectNotEmpty,Be as isString,Ge as isUndefined,p as objectWithContext,Me as registerRule,v as reportError,V as schema,w as shouldApplyConditional,Ne as unique,J as validate,se as validateField,$e as validateFieldAsync,f as withConditionals};
|
|
1
|
+
export{schema}from"./schema";export{applyConditionals,objectWithContext,shouldApplyConditional,withConditionals}from"./schema";export*from"@stacksjs/ts-validation";export*from"./input";export*from"./reporter";export*from"./validator";export function isString(value){return typeof value==="string"}export function isNumber(value){return typeof value==="number"&&!Number.isNaN(value)}export function isBoolean(value){return typeof value==="boolean"}export function isObject(value){return typeof value==="object"&&value!==null&&!Array.isArray(value)}export function isArray(value){return Array.isArray(value)}export function isFunction(value){return typeof value==="function"}export function isUndefined(value){return value===void 0}export function isNull(value){return value===null}export function isNullOrUndefined(value){return value===null||value===void 0}
|
package/dist/input.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare function readJsonObject(request: { json: () => Promise<unknown> }): Promise<Record<string, unknown>>;
|
|
2
|
+
export declare function parsePositiveId(value: unknown): number;
|
|
3
|
+
export declare function parseTextInput(value: unknown, field: string, required?: boolean): string | null;
|
|
4
|
+
export declare function parseNumberInput(value: unknown, field: string, min: number, max: number, integer?: boolean): number | null;
|
|
5
|
+
/** Supports form booleans without treating arbitrary text or objects as consent. */
|
|
6
|
+
export declare function parseBooleanInput(value: unknown, field: string): boolean | undefined;
|
|
7
|
+
/** Validate supplied fields only, so partial updates use the model's own rules. */
|
|
8
|
+
export declare function validateModelInput(model: InputValidatableModel, values: Record<string, unknown>): void;
|
|
9
|
+
export declare interface InputValidatableModel {
|
|
10
|
+
getDefinition: () => { attributes?: Record<string, { validation?: { rule?: { validate(value: unknown): { valid: boolean } } } }> }
|
|
11
|
+
}
|
|
12
|
+
/** Validation failures at a custom action boundary retain their HTTP status. */
|
|
13
|
+
export declare class InputValidationError extends Error {
|
|
14
|
+
readonly status: number;
|
|
15
|
+
}
|
package/dist/input.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export class InputValidationError extends Error{status=422}export async function readJsonObject(request){let body;try{body=await request.json()}catch{throw new InputValidationError("Send a valid JSON object")}if(!body||typeof body!=="object"||Array.isArray(body))throw new InputValidationError("Send a JSON object");return body}export function parsePositiveId(value){if(typeof value!=="string"&&typeof value!=="number")throw new InputValidationError("Invalid identifier");const id=Number(value);if(!Number.isSafeInteger(id)||id<=0)throw new InputValidationError("Invalid identifier");return id}export function parseTextInput(value,field,required=!1){if(value==null&&!required)return null;if(typeof value!=="string")throw new InputValidationError(`${field} must be text`);const text=value.trim();if(!text&&required)throw new InputValidationError(`${field} is required`);return text||null}export function parseNumberInput(value,field,min,max,integer=!1){if(value==null||value==="")return null;if(typeof value!=="number"&&typeof value!=="string"||typeof value==="string"&&!value.trim())throw new InputValidationError(`${field} must be a number`);const number=Number(value);if(!Number.isFinite(number)||number<min||number>max||integer&&!Number.isSafeInteger(number))throw new InputValidationError(`${field} is out of range`);return number}export function parseBooleanInput(value,field){if(value===void 0)return;if(value===!0||value==="true"||value===1||value==="1")return!0;if(value===!1||value==="false"||value===0||value==="0")return!1;throw new InputValidationError(`${field} must be a boolean`)}export function validateModelInput(model,values){for(const[field,value]of Object.entries(values)){const rule=model.getDefinition().attributes?.[field]?.validation?.rule;if(rule&&!rule.validate(value).valid)throw new InputValidationError(`Invalid ${field.replaceAll("_"," ")}`)}}
|
package/dist/is.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare function isDef<T = any>(val?: T): val is T;
|
|
2
|
+
export declare function isBoolean(val: unknown): val is boolean;
|
|
3
|
+
// eslint-disable-next-line ts/no-unsafe-function-type
|
|
4
|
+
export declare function isFunction<T extends Function>(val: unknown): val is T;
|
|
5
|
+
export declare function isNumber(val: unknown): val is number;
|
|
6
|
+
export declare function isString(val: unknown): val is string;
|
|
7
|
+
export declare function isObject(val: unknown): val is object;
|
|
8
|
+
export declare function isWindow(val: unknown): boolean;
|
|
9
|
+
export declare function isMap(val: unknown): val is Map<any, any>;
|
|
10
|
+
export declare function isSet(val: unknown): val is Set<any>;
|
|
11
|
+
export declare function isPromise<T = any>(val: unknown): val is Promise<T>;
|
|
12
|
+
export declare function isUndefined(v: unknown): boolean;
|
|
13
|
+
export declare function isNull(v: unknown): boolean;
|
|
14
|
+
export declare function isSymbol(v: unknown): boolean;
|
|
15
|
+
export declare function isDate(v: unknown): boolean;
|
|
16
|
+
export declare function isRegExp(v: unknown): boolean;
|
|
17
|
+
export declare function isArray(v: unknown): boolean;
|
|
18
|
+
export declare function isPrimitive(v: unknown): boolean;
|
|
19
|
+
export declare function isInteger(v: unknown): boolean;
|
|
20
|
+
export declare function isFloat(v: unknown): boolean;
|
|
21
|
+
export declare function isPositive(v: unknown): boolean;
|
|
22
|
+
export declare function isNegative(v: unknown): boolean;
|
|
23
|
+
export declare function isEven(v: unknown): boolean;
|
|
24
|
+
export declare function isOdd(v: unknown): boolean;
|
|
25
|
+
export declare function isEvenOrOdd(v: unknown): 'even' | 'odd';
|
|
26
|
+
export declare function isPositiveOrNegative(v: unknown): 'positive' | 'negative';
|
|
27
|
+
export declare function isIntegerOrFloat(v: unknown): 'integer' | 'float';
|
|
28
|
+
export declare const isBrowser: boolean;
|
|
29
|
+
export declare const isServer: boolean;
|
package/dist/is.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{toString}from"@stacksjs/strings";import{getTypeName}from"@stacksjs/types";export function isDef(val){return typeof val<"u"}export function isBoolean(val){return typeof val==="boolean"}export function isFunction(val){return typeof val==="function"}export function isNumber(val){return typeof val==="number"}export function isString(val){return typeof val==="string"}export function isObject(val){return toString(val)==="[object Object]"}export function isWindow(val){return typeof window<"u"&&toString(val)==="[object Window]"}export const isBrowser=typeof window<"u",isServer=typeof document>"u";export function isMap(val){return toString(val)==="[object Map]"}export function isSet(val){return toString(val)==="[object Set]"}export function isPromise(val){return toString(val)==="[object Promise]"}export function isUndefined(v){return getTypeName(v)==="undefined"}export function isNull(v){return getTypeName(v)==="null"}export function isSymbol(v){return getTypeName(v)==="symbol"}export function isDate(v){return getTypeName(v)==="date"}export function isRegExp(v){return getTypeName(v)==="regexp"}export function isArray(v){return getTypeName(v)==="array"}export function isPrimitive(v){const type=getTypeName(v);return type==="null"||type==="undefined"||type==="string"||type==="number"||type==="boolean"||type==="symbol"}export function isInteger(v){return isNumber(v)&&Number.isInteger(v)}export function isFloat(v){return isNumber(v)&&!Number.isInteger(v)}export function isPositive(v){return isNumber(v)&&v>0}export function isNegative(v){return isNumber(v)&&v<0}export function isEven(v){return isNumber(v)&&v%2===0}export function isOdd(v){return isNumber(v)&&v%2!==0}export function isEvenOrOdd(v){return isNumber(v)?v%2===0?"even":"odd":"odd"}export function isPositiveOrNegative(v){return isNumber(v)?v>0?"positive":"negative":"negative"}export function isIntegerOrFloat(v){return isNumber(v)?Number.isInteger(v)?"integer":"float":"float"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{applyConditionals,withConditionals}from"./conditional";export function objectWithContext(initialShape){let shape=initialShape??{},isRequired=!0;const validator={name:"object",get isRequired(){return isRequired},getRules:()=>[],test(value){return this.validate(value).valid},isPartOfShape:!1,shape(next){shape=next;return validator},getShape(){return shape},required(){isRequired=!0;return augmented},optional(){isRequired=!1;return augmented},validate(value){const isEmpty=value===void 0||value===null;if(!isRequired&&isEmpty)return{valid:!0,errors:this.isPartOfShape?{}:[]};if(isRequired&&isEmpty){const message="value is required";return this.isPartOfShape?{valid:!1,errors:{object:[{message}]}}:{valid:!1,errors:[{message}]}}if(typeof value!=="object"||Array.isArray(value)){const message="Must be an object";return this.isPartOfShape?{valid:!1,errors:{object:[{message}]}}:{valid:!1,errors:[{message}]}}const parent=value,errors={};let hasErrors=!1;for(const key of Object.keys(shape)){const baseChild=shape[key];if(!baseChild)continue;const child=applyConditionals(baseChild,parent),fieldValue=parent[key],result=child.validate(fieldValue);if(result.valid)continue;hasErrors=!0;if(!Array.isArray(result.errors))for(const[nestedKey,nestedErrors]of Object.entries(result.errors))errors[`${key}.${nestedKey}`]=nestedErrors;else errors[key]=result.errors}if(hasErrors)return{valid:!1,errors};return{valid:!0,errors:{}}}},augmented=withConditionals(validator);return augmented}
|
package/dist/reporter.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
let messages=[];export function reportError(errors){messages=errors}export function getErrors(){return messages}
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import{MessageProvider as C,setCustomMessages as S}from"@stacksjs/ts-validation";import{HttpError as f}from"@stacksjs/error-handling/http-error";function j(e,t,n){if(!this.__conditionals)this.__conditionals=[];return this.__conditionals.push({field:e,match:t,refine:n}),this}function w(){return this.optional(),this}function V(e){let t=e;return t.when=j,t.sometimes=w,t}function R(e,t){if(t===null||t===void 0)return!1;let n=t[e.field];if(typeof e.match==="function")return e.match(n);return n===e.match}function h(e,t){let n=e.__conditionals;if(!n||n.length===0)return e;let a=null;for(let i of n){if(!R(i,t))continue;if(a===null)a=x(e);a=i.refine(a)}return a??e}function x(e){let t=Object.create(Object.getPrototypeOf(e));Object.assign(t,e);let n=e.rules;if(Array.isArray(n))t.rules=n.slice();return t}function y(e){let t=e??{},n=!0,a={name:"object",get isRequired(){return n},getRules:()=>[],test(o){return this.validate(o).valid},isPartOfShape:!1,shape(o){return t=o,a},getShape(){return t},required(){return n=!0,i},optional(){return n=!1,i},validate(o){let r=o===void 0||o===null;if(!n&&r)return{valid:!0,errors:this.isPartOfShape?{}:[]};if(n&&r)return this.isPartOfShape?{valid:!1,errors:{object:[{message:"value is required"}]}}:{valid:!1,errors:[{message:"value is required"}]};if(typeof o!=="object"||Array.isArray(o))return this.isPartOfShape?{valid:!1,errors:{object:[{message:"Must be an object"}]}}:{valid:!1,errors:[{message:"Must be an object"}]};let s=o,d={},u=!1;for(let l of Object.keys(t)){let p=t[l];if(!p)continue;let g=h(p,s),m=s[l],c=g.validate(m);if(c.valid)continue;if(u=!0,!Array.isArray(c.errors))for(let[b,k]of Object.entries(c.errors))d[`${l}.${b}`]=k;else d[l]=c.errors}if(u)return{valid:!1,errors:d};return{valid:!0,errors:{}}}},i=V(a);return i}var O=y();async function T(e){if(!e||typeof e!=="object")return{};if(typeof e.all==="function"){let a=e.all();return a instanceof Promise?await a:a}let t=e,n={};if(t.url)try{new URL(t.url).searchParams.forEach((i,o)=>{n[o]=i})}catch{}if(t.query&&typeof t.query==="object")Object.assign(n,t.query);if(t.jsonBody&&typeof t.jsonBody==="object")Object.assign(n,t.jsonBody);if(t.formBody&&typeof t.formBody==="object")Object.assign(n,t.formBody);if(t.params&&typeof t.params==="object")Object.assign(n,t.params);if(Object.keys(n).length===0&&!("all"in e)&&!("jsonBody"in e))return e;return n}async function L(e,t){let n=await T(e),a={},i;for(let[o,r]of Object.entries(t)){if(!r)continue;if(typeof r.rule==="object"||typeof r.rule==="function"){a[o]=r.rule;let s=r.message;if(typeof s==="string")(i??={})[`${o}.default`]=s;else if(s&&typeof s==="object"){i??={};for(let[d,u]of Object.entries(s))i[`${o}.${d}`]=u}}else a[o]=r}try{if(i)S(new C(i));let o=await O.shape(a).validate(n);if(!o.valid){let s=P(o.errors);throw new f(422,"Validation failed",{errors:s})}return o.value??n}catch(o){if(v(o))throw o;throw new f(500,A(o)||"An unexpected validation error occurred")}}function v(e){return e instanceof f||e instanceof Error&&typeof e.status==="number"}function A(e){if(e instanceof Error)return e.message;if(typeof e==="string")return e;if(typeof e==="number"||typeof e==="boolean")return String(e);if(e&&typeof e==="object"&&"message"in e&&typeof e.message==="string")return e.message;if(e==null)return"Unknown error";try{return String(e)}catch{return"Unknown error"}}function P(e){let t={};if(Array.isArray(e)){for(let n of e){if(!n||typeof n!=="object")continue;let a=typeof n.field==="string"?n.field:"_",i=typeof n.message==="string"?n.message:"Validation failed";(t[a]??=[]).push(i)}return t}if(!e||typeof e!=="object")return t;for(let[n,a]of Object.entries(e)){let i=Array.isArray(a)?a:[a];for(let o of i){let r=typeof o==="string"?o:o&&typeof o==="object"&&typeof o.message==="string"?o.message:"Validation failed";(t[n]??=[]).push(r)}}return t}export{L as validate};
|
|
1
|
+
import{MessageProvider,setCustomMessages}from"@stacksjs/ts-validation";import{HttpError}from"@stacksjs/error-handling/http-error";import{objectWithContext}from"./object-with-context";const requestObjectValidator=objectWithContext();async function gatherRequestInput(source){if(!source||typeof source!=="object")return{};if(typeof source.all==="function"){const all=source.all();return all instanceof Promise?await all:all}const request=source,input={};if(request.url)try{new URL(request.url).searchParams.forEach((value,key)=>{input[key]=value})}catch{}if(request.query&&typeof request.query==="object")Object.assign(input,request.query);if(request.jsonBody&&typeof request.jsonBody==="object")Object.assign(input,request.jsonBody);if(request.formBody&&typeof request.formBody==="object")Object.assign(input,request.formBody);if(request.params&&typeof request.params==="object")Object.assign(input,request.params);if(Object.keys(input).length===0&&!("all"in source)&&!("jsonBody"in source))return source;return input}export async function validate(request,rules){const input=await gatherRequestInput(request),ruleObject={};let messageObject;for(const[field,definition]of Object.entries(rules)){if(!definition)continue;if(typeof definition.rule==="object"||typeof definition.rule==="function"){ruleObject[field]=definition.rule;const message=definition.message;if(typeof message==="string")(messageObject??={})[`${field}.default`]=message;else if(message&&typeof message==="object"){messageObject??={};for(const[key,value]of Object.entries(message))messageObject[`${field}.${key}`]=value}}else ruleObject[field]=definition}try{if(messageObject)setCustomMessages(new MessageProvider(messageObject));const result=await requestObjectValidator.shape(ruleObject).validate(input);if(!result.valid){const errors=normalizeValidationErrors(result.errors);throw new HttpError(422,"Validation failed",{errors})}return result.value??input}catch(error){if(isHttpErrorLike(error))throw error;throw new HttpError(500,getErrorMessage(error)||"An unexpected validation error occurred")}}function isHttpErrorLike(error){return error instanceof HttpError||error instanceof Error&&typeof error.status==="number"}function getErrorMessage(error){if(error instanceof Error)return error.message;if(typeof error==="string")return error;if(typeof error==="number"||typeof error==="boolean")return String(error);if(error&&typeof error==="object"&&"message"in error&&typeof error.message==="string")return error.message;if(error==null)return"Unknown error";try{return String(error)}catch{return"Unknown error"}}function normalizeValidationErrors(value){const errors={};if(Array.isArray(value)){for(const entry of value){if(!entry||typeof entry!=="object")continue;const field=typeof entry.field==="string"?entry.field:"_",message=typeof entry.message==="string"?entry.message:"Validation failed";(errors[field]??=[]).push(message)}return errors}if(!value||typeof value!=="object")return errors;for(const[field,entries]of Object.entries(value)){const list=Array.isArray(entries)?entries:[entries];for(const entry of list){const message=typeof entry==="string"?entry:entry&&typeof entry==="object"&&typeof entry.message==="string"?entry.message:"Validation failed";(errors[field]??=[]).push(message)}}return errors}
|
package/dist/rules.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thanks to VineJS for the following types:
|
|
3
|
+
*
|
|
4
|
+
* The context shared with the entire validation pipeline.
|
|
5
|
+
* Each field gets its own context object.
|
|
6
|
+
*/
|
|
7
|
+
export declare interface FieldContext {
|
|
8
|
+
value: unknown
|
|
9
|
+
data: any
|
|
10
|
+
meta: Record<string, any>
|
|
11
|
+
mutate: (newValue: any, field: FieldContext) => void
|
|
12
|
+
report: ErrorReporterContract['report']
|
|
13
|
+
isValid: boolean
|
|
14
|
+
isDefined: boolean
|
|
15
|
+
wildCardPath: string
|
|
16
|
+
parent: any
|
|
17
|
+
name: string | number
|
|
18
|
+
isArrayMember: boolean
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Thanks to VineJS for the following types:
|
|
22
|
+
*
|
|
23
|
+
* The error reporter is used to report errors during the validation
|
|
24
|
+
* process.
|
|
25
|
+
*/
|
|
26
|
+
export declare interface ErrorReporterContract {
|
|
27
|
+
hasErrors: boolean
|
|
28
|
+
createError: () => Error
|
|
29
|
+
report: (message: string, rule: string, field: FieldContext, args?: Record<string, any>) => any
|
|
30
|
+
}
|
package/dist/rules.js
ADDED
|
File without changes
|
package/dist/runtime.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import{v as h}from"@stacksjs/ts-validation";var j=new Set(["image/jpeg","image/png","image/webp","image/gif","image/avif"]);function g(e){return e.mimetype??e.mimeType}function k(e){return e.originalName??e.name}function A(e){let t=k(e);if(!t)return null;let n=t.lastIndexOf(".");if(n<=0||n===t.length-1)return null;let a=t.slice(n+1).toLowerCase();return/^[a-z0-9]+$/.test(a)?a:null}class b{_required=!1;_rules=[];required(){return this._required=!0,this}image(){return this._rules.push((e)=>{let t=g(e);if(!t||!j.has(t.toLowerCase()))return`must be an image (got ${t||"no mimetype"})`;return null}),this}mimeTypes(e){let t=new Set(e.map((n)=>n.toLowerCase()));return this._rules.push((n)=>{let a=g(n);if(!a||!t.has(a.toLowerCase()))return`must be one of: ${e.join(", ")} (got ${a||"no mimetype"})`;return null}),this}maxBytes(e){return this._rules.push((t)=>{if(typeof t.size!=="number")return"has no size - cannot enforce maxBytes";if(t.size>e)return`is too large: ${t.size} bytes exceeds the ${e}-byte cap`;return null}),this}minBytes(e){return this._rules.push((t)=>{if(typeof t.size!=="number")return"has no size - cannot enforce minBytes";if(t.size<e)return`is too small: ${t.size} bytes is below the ${e}-byte minimum`;return null}),this}extensions(e){let t=new Set(e.map((n)=>n.toLowerCase().replace(/^\./,"")));return this._rules.push((n)=>{let a=A(n);if(!a||!t.has(a))return`must have one of these extensions: ${e.join(", ")} (got ${a?`.${a}`:"no extension"})`;return null}),this}custom(e){return this._rules.push(e),this}validate(e){if(e===null||e===void 0){if(this._required)return{valid:!1,errors:[{message:"is required"}]};return{valid:!0}}if(typeof e!=="object"||typeof e.size!=="number")return{valid:!1,errors:[{message:"must be an uploaded file"}]};let t=e,n=[];for(let a of this._rules){let i=a(t);if(i)n.push({message:i})}return n.length===0?{valid:!0}:{valid:!1,errors:n}}}function x(){return new b}function O(e,t,n){if(!this.__conditionals)this.__conditionals=[];return this.__conditionals.push({field:e,match:t,refine:n}),this}function R(){return this.optional(),this}function o(e){let t=e;return t.when=O,t.sometimes=R,t}function m(e,t){if(t===null||t===void 0)return!1;let n=t[e.field];if(typeof e.match==="function")return e.match(n);return n===e.match}function u(e,t){let n=e.__conditionals;if(!n||n.length===0)return e;let a=null;for(let i of n){if(!m(i,t))continue;if(a===null)a=W(e);a=i.refine(a)}return a??e}function W(e){let t=Object.create(Object.getPrototypeOf(e));Object.assign(t,e);let n=e.rules;if(Array.isArray(n))t.rules=n.slice();return t}function c(e){let t=e??{},n=!0,a={name:"object",get isRequired(){return n},getRules:()=>[],test(r){return this.validate(r).valid},isPartOfShape:!1,shape(r){return t=r,a},getShape(){return t},required(){return n=!0,i},optional(){return n=!1,i},validate(r){let l=r===void 0||r===null;if(!n&&l)return{valid:!0,errors:this.isPartOfShape?{}:[]};if(n&&l)return this.isPartOfShape?{valid:!1,errors:{object:[{message:"value is required"}]}}:{valid:!1,errors:[{message:"value is required"}]};if(typeof r!=="object"||Array.isArray(r))return this.isPartOfShape?{valid:!1,errors:{object:[{message:"Must be an object"}]}}:{valid:!1,errors:[{message:"Must be an object"}]};let V=r,f={},p=!1;for(let s of Object.keys(t)){let y=t[s];if(!y)continue;let C=u(y,V),I=V[s],d=C.validate(I);if(d.valid)continue;if(p=!0,!Array.isArray(d.errors))for(let[S,w]of Object.entries(d.errors))f[`${s}.${S}`]=w;else f[s]=d.errors}if(p)return{valid:!1,errors:f};return{valid:!0,errors:{}}}},i=o(a);return i}var v=new Set(["array","bigint","binary","blob","boolean","custom","date","datetime","decimal","double","enum","float","integer","json","number","password","smallint","string","text","time","timestamp","timestampTz","unix"]);function N(e){if(e instanceof Date)return!Number.isNaN(e.getTime());if(typeof e!=="string"||!/^\d{4}-\d{2}-\d{2}$/.test(e))return!1;let t=new Date(`${e}T00:00:00.000Z`);return!Number.isNaN(t.getTime())&&t.toISOString().slice(0,10)===e}function _(){let e=h.custom(N,"Must be a valid date");return e.name="date",o(e)}var P=/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?$/;function F(e){if(e instanceof Date)return!Number.isNaN(e.getTime());if(typeof e==="number")return Number.isFinite(e)&&e>=0;if(typeof e!=="string")return!1;let t=e.trim();if(!t)return!1;if(/^\d+$/.test(t))return!0;if(P.test(t))return!Number.isNaN(new Date(t.replace(" ","T")).getTime());return!Number.isNaN(new Date(t).getTime())}function E(){let e=h.custom(F,"Must be a valid timestamp");return e.name="timestamp",o(e)}var T=Object.create(null),L=new Proxy(h,{get(e,t,n){if(t==="file")return x;if(t==="object")return c;if(t==="date")return _;if(t==="timestamp")return E;if(typeof t==="string"){let a=T[t];if(a)return a;if(!v.has(t))return Reflect.get(e,t,n);let i=(...r)=>{let l=Reflect.get(e,t,n);return o(l(...r))};return T[t]=i,i}return Reflect.get(e,t,n)}});export*from"@stacksjs/ts-validation";export{u as applyConditionals,c as objectWithContext,L as schema,m as shouldApplyConditional,o as withConditionals};
|
|
1
|
+
export{schema}from"./schema";export{applyConditionals,objectWithContext,shouldApplyConditional,withConditionals}from"./schema";export*from"@stacksjs/ts-validation";
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{v}from"@stacksjs/ts-validation";import{file,FileValidator}from"./file-validator";import{withConditionals}from"./conditional";import{objectWithContext}from"./object-with-context";const FACTORY_KEYS=new Set(["array","bigint","binary","blob","boolean","custom","date","datetime","decimal","double","enum","float","integer","json","number","password","smallint","string","text","time","timestamp","timestampTz","unix"]);function isValidDateInput(value){if(value instanceof Date)return!Number.isNaN(value.getTime());if(typeof value!=="string"||!/^\d{4}-\d{2}-\d{2}$/.test(value))return!1;const parsed=new Date(`${value}T00:00:00.000Z`);return!Number.isNaN(parsed.getTime())&&parsed.toISOString().slice(0,10)===value}function dateInputFactory(){const validator=v.custom(isValidDateInput,"Must be a valid date");validator.name="date";return withConditionals(validator)}const SQL_DATETIME=/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?$/;function isValidTimestampInput(value){if(value instanceof Date)return!Number.isNaN(value.getTime());if(typeof value==="number")return Number.isFinite(value)&&value>=0;if(typeof value!=="string")return!1;const trimmed=value.trim();if(!trimmed)return!1;if(/^\d+$/.test(trimmed))return!0;if(SQL_DATETIME.test(trimmed))return!Number.isNaN(new Date(trimmed.replace(" ","T")).getTime());return!Number.isNaN(new Date(trimmed).getTime())}function timestampInputFactory(){const validator=v.custom(isValidTimestampInput,"Must be a valid timestamp");validator.name="timestamp";return withConditionals(validator)}const wrappedFactories=Object.create(null);export const schema=new Proxy(v,{get(target,prop,receiver){if(prop==="file")return file;if(prop==="object")return objectWithContext;if(prop==="date")return dateInputFactory;if(prop==="timestamp")return timestampInputFactory;if(typeof prop==="string"){const cached=wrappedFactories[prop];if(cached)return cached;if(!FACTORY_KEYS.has(prop))return Reflect.get(target,prop,receiver);const wrapped=(...args)=>{const factory=Reflect.get(target,prop,receiver);return withConditionals(factory(...args))};wrappedFactories[prop]=wrapped;return wrapped}return Reflect.get(target,prop,receiver)}});export{file,FileValidator}from"./file-validator";export{applyConditionals,shouldApplyConditional,withConditionals}from"./conditional";export{objectWithContext}from"./object-with-context";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Validation types - no longer using VineJS
|
|
2
|
+
export declare interface ValidationResult {
|
|
3
|
+
valid: boolean
|
|
4
|
+
errors?: Array<{ message: string }>
|
|
5
|
+
}
|
|
6
|
+
export declare interface ValidationRule {
|
|
7
|
+
validate: (value: unknown) => ValidationResult
|
|
8
|
+
}
|
|
9
|
+
export type ValidationBoolean = ValidationRule;
|
|
10
|
+
export type ValidationEnum = ValidationRule;
|
|
11
|
+
export type ValidationNumber = ValidationRule;
|
|
12
|
+
export type ValidationString = ValidationRule;
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{HttpError}from"@stacksjs/error-handling";import{path}from"@stacksjs/path";import{snakeCase}from"@stacksjs/strings";import{getErrorMessage}from"@stacksjs/utils";import{MessageProvider,setCustomMessages}from"@stacksjs/ts-validation";import{reportError}from"./reporter";import{schema}from"./schema";export{validate}from"./request-validator";function globSync(patterns,options={}){const results=[];for(const pattern of patterns){const slash=pattern.startsWith("/")?pattern.lastIndexOf("/"):-1,wildcardAt=pattern.indexOf("*"),splitAt=wildcardAt===-1?slash:pattern.lastIndexOf("/",wildcardAt),cwd=splitAt>0?pattern.slice(0,splitAt):".",rel=splitAt>0?pattern.slice(splitAt+1):pattern,glob=new Bun.Glob(rel);for(const match of glob.scanSync({cwd,onlyFiles:!0}))results.push(options.absolute?`${cwd}/${match}`:match)}return results}export function isObjectNotEmpty(obj){if(obj===void 0)return!1;return Object.keys(obj).length>0}function toMessageObjects(errors,params){const valueFor=(field)=>{const value=params[field];return value===void 0||value===null?"":String(value)};if(Array.isArray(errors))return errors.map((error)=>({message:error.message,value:"",field:""}));return Object.entries(errors).flatMap(([field,fieldErrors])=>fieldErrors.map((error)=>({message:error.message,value:valueFor(field),field})))}export async function validateField(modelFile,params){const modelPath=globSync([path.userModelsPath("*.ts"),path.storagePath("framework/defaults/app/Models/**/*.ts")],{absolute:!0}).find((file)=>file.endsWith(`${modelFile}.ts`));if(!modelPath)throw new HttpError(404,`Model ${modelFile} not found`);const attributes=(await import(modelPath)).default.attributes,ruleObject={},messageObject={};for(const key in attributes)if(Object.prototype.hasOwnProperty.call(attributes,key)){const attributeKey=attributes[key];if(!attributeKey)continue;const isRequired="isRequired"in(attributeKey.validation?.rule??{})?(attributeKey.validation?.rule).isRequired:!1;if(attributeKey.default!==void 0&&isRequired===!1)continue;ruleObject[snakeCase(key)]=attributeKey.validation?.rule;const validatorMessages=attributeKey.validation?.message;if(validatorMessages)for(const validatorMessageKey in validatorMessages){const validatorMessageString=`${key}.${validatorMessageKey}`;messageObject[validatorMessageString]=validatorMessages[validatorMessageKey]||""}}try{setCustomMessages(new MessageProvider(messageObject));const result=await schema.object(ruleObject).validate(params);if(!result.valid){reportError(toMessageObjects(result.errors,params));throw new HttpError(422,"Validation failed",{errors:result.errors})}return result}catch(error){if(isHttpErrorLike(error))throw error;throw new HttpError(500,getErrorMessage(error)||"An unexpected validation error occurred")}}const customRuleRegistry=new Map;export function registerRule(name,rule){customRuleRegistry.set(name,rule)}export function getCustomRule(name){return customRuleRegistry.get(name)}export function unique(table,column,exceptId){return async(value)=>{try{const{db}=await import("@stacksjs/database/runtime");let query=db.selectFrom(table).where(column,"=",value);if(exceptId)query=query.where("id","!=",exceptId);return await query.selectAll().executeTakeFirst()?`The ${column} has already been taken`:!0}catch{return!0}}}export function exists(table,column){return async(value)=>{try{const{db}=await import("@stacksjs/database/runtime");return await db.selectFrom(table).where(column,"=",value).selectAll().executeTakeFirst()?!0:`The selected ${column} does not exist`}catch{return!0}}}export async function validateFieldAsync(modelFile,params,asyncRules){const syncResult=await validateField(modelFile,params);if(asyncRules&&Object.keys(asyncRules).length>0){const asyncErrors={};await Promise.all(Object.entries(asyncRules).map(async([field,validator])=>{const result=await validator(params[field],params);if(result!==!0)asyncErrors[field]=typeof result==="string"?result:`${field} validation failed`}));if(Object.keys(asyncErrors).length>0)throw new HttpError(422,"Validation failed",{errors:asyncErrors})}return syncResult}export async function customValidate(attributes,params){const ruleObject={},messageObject={};for(const key in attributes)if(Object.prototype.hasOwnProperty.call(attributes,key)){const rule=attributes[key]?.rule;if(rule)ruleObject[key]=rule;const validatorMessages=attributes[key]?.message;for(const validatorMessageKey in validatorMessages){const validatorMessageString=`${key}.${validatorMessageKey}`;messageObject[validatorMessageString]=attributes[key]?.message[validatorMessageKey]||""}}try{const result=await schema.object().shape(ruleObject).validate(params);if(!result.valid)throw new HttpError(422,"Validation failed",{errors:result.errors});return result}catch(error){if(isHttpErrorLike(error))throw error;throw new HttpError(500,getErrorMessage(error)||"An unexpected validation error occurred")}}function isHttpErrorLike(error){return error instanceof HttpError||error instanceof Error&&typeof error.status==="number"}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/validation",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.75.
|
|
5
|
+
"version": "0.75.110",
|
|
6
6
|
"description": "The Stacks Validation ways.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -64,18 +64,18 @@
|
|
|
64
64
|
"prepublishOnly": "bun run build"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@stacksjs/error-handling": "0.75.
|
|
68
|
-
"@stacksjs/path": "0.75.
|
|
69
|
-
"@stacksjs/strings": "0.75.
|
|
67
|
+
"@stacksjs/error-handling": "0.75.110",
|
|
68
|
+
"@stacksjs/path": "0.75.110",
|
|
69
|
+
"@stacksjs/strings": "0.75.110",
|
|
70
70
|
"@stacksjs/ts-validation": "^0.5.6",
|
|
71
|
-
"@stacksjs/types": "0.75.
|
|
72
|
-
"@stacksjs/utils": "0.75.
|
|
71
|
+
"@stacksjs/types": "0.75.110",
|
|
72
|
+
"@stacksjs/utils": "0.75.110"
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
75
|
"better-dx": "^0.2.24"
|
|
76
76
|
},
|
|
77
77
|
"peerDependencies": {
|
|
78
|
-
"@stacksjs/database": "0.75.
|
|
78
|
+
"@stacksjs/database": "0.75.110"
|
|
79
79
|
},
|
|
80
80
|
"peerDependenciesMeta": {
|
|
81
81
|
"@stacksjs/database": {
|