@stacksjs/validation 0.70.88 → 0.70.90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,59 @@
1
+ import type { Validator } from '@stacksjs/ts-validation';
2
+ /**
3
+ * Mixin: add `.when()` / `.sometimes()` to a validator instance.
4
+ *
5
+ * Mutates the validator in place (assigning the new methods) so the
6
+ * existing chainable surface (`.required()`, `.minLength()`, etc.)
7
+ * keeps returning `this`. Returns the same instance with the augmented
8
+ * type so callers see the new methods on the fluent chain.
9
+ */
10
+ export declare function withConditionals<V extends Validator<any>>(validator: V): V & ConditionalAPI<V>;
11
+ /**
12
+ * Evaluate a single `when` clause against a parent object.
13
+ *
14
+ * Returns `true` when the conditional should fire (and its refinement
15
+ * should be applied). Pure function — no side effects, safe to call
16
+ * during introspection too.
17
+ */
18
+ export declare function shouldApplyConditional(record: ConditionalRecord<Validator<any>>, parent: Record<string, unknown> | null | undefined): boolean;
19
+ /**
20
+ * Apply every matching conditional on `base` against `parent`, in
21
+ * registration order. Returns the (possibly refined) validator that
22
+ * should be used to validate the field.
23
+ *
24
+ * The base validator is NEVER mutated — ts-validation methods like
25
+ * `.required()` and `.min(3)` MUTATE `this` and return it, so calling
26
+ * refine functions directly on the base would leak across `validate()`
27
+ * invocations (each call would compound the previous refinements,
28
+ * making "VAT required for EU customers" sticky across requests).
29
+ * {@link cloneValidator} produces an isolated instance the refine
30
+ * pipeline can safely mutate without polluting the schema definition.
31
+ */
32
+ export declare function applyConditionals<V extends Validator<any>>(base: V, parent: Record<string, unknown> | null | undefined): V;
33
+ /**
34
+ * A single `.when()` clause attached to a field validator. Multiple
35
+ * `.when()` calls stack — they're evaluated in registration order and
36
+ * each matching one further refines the base validator.
37
+ */
38
+ export declare interface ConditionalRecord<V extends Validator<any>> {
39
+ field: string
40
+ match: unknown | ((value: unknown) => boolean)
41
+ refine: (validator: V) => V
42
+ }
43
+ /** Anything that has a mutable conditionals array — duck-typed so this
44
+ * works across all the ts-validation validator subclasses. */
45
+ export declare interface ValidatorWithConditionals<V extends Validator<any>> {
46
+ __conditionals?: ConditionalRecord<V>[]
47
+ }
48
+ /**
49
+ * Conditional-rules surface — apps reach these methods through
50
+ * `.when()` / `.sometimes()` on any validator the schema proxy returns.
51
+ */
52
+ export declare interface ConditionalAPI<V extends Validator<any>> {
53
+ when: (
54
+ field: string,
55
+ match: unknown | ((value: unknown) => boolean),
56
+ refine: (validator: V) => V,
57
+ ) => V & ConditionalAPI<V>
58
+ sometimes: () => V & ConditionalAPI<V>
59
+ }
@@ -0,0 +1,65 @@
1
+ /** Factory matching ts-validation's `v.string()` / `v.number()` style. */
2
+ export declare function file(): FileValidator;
3
+ /**
4
+ * `schema.file()` — chainable validator for uploaded files
5
+ * (stacksjs/stacks#1856).
6
+ *
7
+ * Mirrors the ergonomics of `schema.string()` / `schema.number()` /
8
+ * `schema.enum()` from ts-validation but targets the `UploadedFile`
9
+ * shape that `req.file('avatar')` / `req.files` return. Conforms to the
10
+ * `{ rule: { validate(value): { valid, errors? } } }` contract that the
11
+ * Action layer ({@link `@stacksjs/router`} → `validateActionInput`)
12
+ * iterates, so file validations slot into the same `validations:` block
13
+ * as the rest of the field rules:
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * new Action({
18
+ * method: 'POST',
19
+ * validations: {
20
+ * avatar: { rule: schema.file().image().maxBytes(2 * 1024 * 1024) },
21
+ * },
22
+ * async handle(req) {
23
+ * const file = req.file('avatar')!
24
+ * const { url } = await Storage.put(file, { disk: 'public', dir: 'avatars' })
25
+ * // …
26
+ * },
27
+ * })
28
+ * ```
29
+ *
30
+ * The validator is intentionally narrow on the input it accepts: a
31
+ * structural shape with `size: number` and either `mimetype` or
32
+ * `mimeType`, optionally with `originalName` / `name`. Both the
33
+ * router's wrapping `UploadedFile` class and the raw multipart-parse
34
+ * shape satisfy it.
35
+ */
36
+ /** Shape this validator runs against. */
37
+ export declare interface FileLike {
38
+ size: number
39
+ mimetype?: string
40
+ mimeType?: string
41
+ originalName?: string
42
+ name?: string
43
+ }
44
+ declare interface ValidationError { message: string }
45
+ declare interface ValidationResult { valid: boolean, errors?: ValidationError[] }
46
+ /**
47
+ * Chainable file validator. Each method returns `this` so callers can
48
+ * stack constraints in any order; rules accumulate into an internal
49
+ * list and run sequentially on `.validate()`.
50
+ *
51
+ * Designed to be cheap to construct — most validators in an Action's
52
+ * `validations:` block are built once at module load. No I/O happens
53
+ * here; image dimension / content-sniffing rules belong in a separate
54
+ * `@stacksjs/storage/image` opt-in (deliverable 5 of #1856).
55
+ */
56
+ export declare class FileValidator {
57
+ required(): this;
58
+ image(): this;
59
+ mimeTypes(allowed: string[]): this;
60
+ maxBytes(max: number): this;
61
+ minBytes(min: number): this;
62
+ extensions(allowed: string[]): this;
63
+ custom(rule: (file: FileLike) => string | null): this;
64
+ validate(value: unknown): ValidationResult;
65
+ }
@@ -0,0 +1,40 @@
1
+ export type {
2
+ ConditionalAPI,
3
+ ConditionalRecord,
4
+ ObjectWithContextValidator,
5
+ ValidatorWithConditionals,
6
+ } from './schema';
7
+ // Type guard utilities
8
+ export declare function isString(value: unknown): value is string;
9
+ export declare function isNumber(value: unknown): value is number;
10
+ export declare function isBoolean(value: unknown): value is boolean;
11
+ export declare function isObject(value: unknown): value is Record<string, unknown>;
12
+ export declare function isArray(value: unknown): value is unknown[];
13
+ export declare function isFunction(value: unknown): value is Function;
14
+ export declare function isUndefined(value: unknown): value is undefined;
15
+ export declare function isNull(value: unknown): value is null;
16
+ export declare function isNullOrUndefined(value: unknown): value is null | undefined;
17
+ // Order matters: bind `schema` first so any consumer that imports it
18
+ // transitively — validator.ts → schema, plus any user model that lands
19
+ // inside the auto-imports barrel — sees the Proxy rather than an empty
20
+ // namespace placeholder. With this export below the others, ESM consumers
21
+ // arriving mid-evaluation through the auto-imports graph would race the
22
+ // `export *` re-exports from `@stacksjs/ts-validation` (which also exports
23
+ // a `schema` symbol) and see whichever binding was registered first.
24
+ export { schema } from './schema';
25
+ // Conditional validation surface — `.when()` / `.sometimes()` mixins +
26
+ // context-aware object validator (stacksjs/stacks#1890).
27
+ export {
28
+ applyConditionals,
29
+ objectWithContext,
30
+ shouldApplyConditional,
31
+ withConditionals,
32
+ } from './schema';
33
+ // Re-export everything from @stacksjs/ts-validation. The explicit `schema`
34
+ // export above shadows ts-validation's own `schema` for `import { schema }
35
+ // from '@stacksjs/validation'` consumers — which is what we want, since the
36
+ // Proxy keeps `schema.<method>()` reactive across module-graph races.
37
+ export * from '@stacksjs/ts-validation';
38
+ // Export local validation helpers
39
+ export * from './reporter';
40
+ export * from './validator';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ // @bun
2
+ var H=import.meta.require;import{v as b}from"@stacksjs/ts-validation";var h=new Set(["image/jpeg","image/png","image/webp","image/gif","image/avif"]);function K(B){return B.mimetype??B.mimeType}function f(B){return B.originalName??B.name}function g(B){let D=f(B);if(!D)return null;let J=D.lastIndexOf(".");if(J<=0||J===D.length-1)return null;let L=D.slice(J+1).toLowerCase();return/^[a-z0-9]+$/.test(L)?L:null}class F{_required=!1;_rules=[];required(){return this._required=!0,this}image(){return this._rules.push((B)=>{let D=K(B);if(!D||!h.has(D.toLowerCase()))return`must be an image (got ${D||"no mimetype"})`;return null}),this}mimeTypes(B){let D=new Set(B.map((J)=>J.toLowerCase()));return this._rules.push((J)=>{let L=K(J);if(!L||!D.has(L.toLowerCase()))return`must be one of: ${B.join(", ")} (got ${L||"no mimetype"})`;return null}),this}maxBytes(B){return this._rules.push((D)=>{if(typeof D.size!=="number")return"has no size \u2014 cannot enforce maxBytes";if(D.size>B)return`is too large: ${D.size} bytes exceeds the ${B}-byte cap`;return null}),this}minBytes(B){return this._rules.push((D)=>{if(typeof D.size!=="number")return"has no size \u2014 cannot enforce minBytes";if(D.size<B)return`is too small: ${D.size} bytes is below the ${B}-byte minimum`;return null}),this}extensions(B){let D=new Set(B.map((J)=>J.toLowerCase().replace(/^\./,"")));return this._rules.push((J)=>{let L=g(J);if(!L||!D.has(L))return`must have one of these extensions: ${B.join(", ")} (got ${L?`.${L}`:"no extension"})`;return null}),this}custom(B){return this._rules.push(B),this}validate(B){if(B===null||B===void 0){if(this._required)return{valid:!1,errors:[{message:"is required"}]};return{valid:!0}}if(typeof B!=="object"||typeof B.size!=="number")return{valid:!1,errors:[{message:"must be an uploaded file"}]};let D=B,J=[];for(let L of this._rules){let U=L(D);if(U)J.push({message:U})}return J.length===0?{valid:!0}:{valid:!1,errors:J}}}function q(){return new F}function T(B){let D=B;return D.when=function(L,U,Q){if(!this.__conditionals)this.__conditionals=[];return this.__conditionals.push({field:L,match:U,refine:Q}),this},D.sometimes=function(){return this.optional(),this},D}function A(B,D){if(D===null||D===void 0)return!1;let J=D[B.field];if(typeof B.match==="function")return B.match(J);return J===B.match}function V(B,D){let J=B.__conditionals;if(!J||J.length===0)return B;let L=null;for(let U of J){if(!A(U,D))continue;if(L===null)L=y(B);L=U.refine(L)}return L??B}function y(B){let D=Object.create(Object.getPrototypeOf(B));Object.assign(D,B);let J=B.rules;if(Array.isArray(J))D.rules=J.slice();return D}function I(B){let D=B??{},J=!0,L={name:"object",get isRequired(){return J},getRules:()=>[],test(Q){return this.validate(Q).valid},isPartOfShape:!1,shape(Q){return D=Q,L},getShape(){return D},required(){return J=!0,U},optional(){return J=!1,U},validate(Q){let Z=Q===void 0||Q===null;if(!J&&Z)return{valid:!0,errors:this.isPartOfShape?{}:[]};if(J&&Z)return this.isPartOfShape?{valid:!1,errors:{object:[{message:"value is required"}]}}:{valid:!1,errors:[{message:"value is required"}]};if(typeof Q!=="object"||Array.isArray(Q))return this.isPartOfShape?{valid:!1,errors:{object:[{message:"Must be an object"}]}}:{valid:!1,errors:[{message:"Must be an object"}]};let $=Q,X={},G=!1;for(let N of Object.keys(D)){let _=D[N];if(!_)continue;let Y=V(_,$),S=$[N],P=Y.validate(S);if(P.valid)continue;if(G=!0,!Array.isArray(P.errors))for(let[w,k]of Object.entries(P.errors))X[`${N}.${w}`]=k;else X[N]=P.errors}if(G)return{valid:!1,errors:X};return{valid:!0,errors:{}}}},U=T(L);return U}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 m(B){return(...D)=>{let J=B(...D);return T(J)}}var W=new Proxy(b,{get(B,D,J){if(D==="file")return q;if(D==="object")return I;if(typeof D==="string"&&v.has(D)){let L=Reflect.get(B,D,J);if(typeof L==="function")return m(L)}return Reflect.get(B,D,J)}});export*from"@stacksjs/ts-validation";var M=[];function O(B){M=B}function UB(){return M}import{HttpError as z}from"@stacksjs/error-handling";import{path as R}from"@stacksjs/path";import{snakeCase as p}from"@stacksjs/strings";import{getErrorMessage as C}from"@stacksjs/utils";import{MessageProvider as E,setCustomMessages as x}from"@stacksjs/ts-validation";function c(B,D={}){let J=[];for(let L of B){let Q=L.startsWith("/")?L.lastIndexOf("/"):-1,Z=L.indexOf("*"),$=Z===-1?Q:L.lastIndexOf("/",Z),X=$>0?L.slice(0,$):".",G=$>0?L.slice($+1):L,N=new Bun.Glob(G);for(let _ of N.scanSync({cwd:X,onlyFiles:!0}))J.push(D.absolute?`${X}/${_}`:_)}return J}function WB(B){if(B===void 0)return!1;return Object.keys(B).length>0}async function d(B,D){let L=c([R.userModelsPath("*.ts"),R.storagePath("framework/defaults/app/Models/**/*.ts")],{absolute:!0}).find((X)=>X.endsWith(`${B}.ts`));if(!L)throw new z(404,`Model ${B} not found`);let Q=(await import(L)).default.attributes,Z={},$={};for(let X in Q)if(Object.prototype.hasOwnProperty.call(Q,X)){let G=Q[X];if(!G)continue;let N="isRequired"in(G.validation?.rule??{})?(G.validation?.rule).isRequired:!1;if(G.default!==void 0&&N===!1)continue;Z[p(X)]=G.validation?.rule;let _=G.validation?.message;if(_)for(let Y in _){let S=`${X}.${Y}`;$[S]=_[Y]||""}}try{x(new E($));let G=await W.object(Z).validate(D);if(!G.valid)throw O(G.errors),new z(422,"Validation failed",{errors:G.errors});return G}catch(X){if(X instanceof z)throw X;throw new z(500,C(X)||"An unexpected validation error occurred")}}var j=new Map;function YB(B,D){j.set(B,D)}function PB(B){return j.get(B)}function VB(B,D,J){return async(L)=>{try{let{db:U}=await import("@stacksjs/database"),Q=U.selectFrom(B).where(D,"=",L);if(J)Q=Q.where("id","!=",J);return await Q.selectAll().executeTakeFirst()?`The ${D} has already been taken`:!0}catch{return!0}}}function IB(B,D){return async(J)=>{try{let{db:L}=await import("@stacksjs/database");return await L.selectFrom(B).where(D,"=",J).selectAll().executeTakeFirst()?!0:`The selected ${D} does not exist`}catch{return!0}}}async function SB(B,D,J){let L=await d(B,D);if(J&&Object.keys(J).length>0){let U={};if(await Promise.all(Object.entries(J).map(async([Q,Z])=>{let $=await Z(D[Q],D);if($!==!0)U[Q]=typeof $==="string"?$:`${Q} validation failed`})),Object.keys(U).length>0)throw new z(422,"Validation failed",{errors:U})}return L}async function n(B){if(!B||typeof B!=="object")return{};if(typeof B.all==="function"){let L=B.all();return L instanceof Promise?await L:L}let D=B,J={};if(D.url)try{new URL(D.url).searchParams.forEach((U,Q)=>{J[Q]=U})}catch{}if(D.query&&typeof D.query==="object")Object.assign(J,D.query);if(D.jsonBody&&typeof D.jsonBody==="object")Object.assign(J,D.jsonBody);if(D.formBody&&typeof D.formBody==="object")Object.assign(J,D.formBody);if(D.params&&typeof D.params==="object")Object.assign(J,D.params);if(Object.keys(J).length===0&&!("all"in B)&&!("jsonBody"in B))return B;return J}async function AB(B,D){let J=await n(B),L={},U={};for(let[Q,Z]of Object.entries(D)){if(!Z)continue;if(typeof Z.rule==="object"||typeof Z.rule==="function"){L[Q]=Z.rule;let $=Z.message;if(typeof $==="string")U[`${Q}.default`]=$;else if($&&typeof $==="object")for(let[X,G]of Object.entries($))U[`${Q}.${X}`]=G}else L[Q]=Z}try{if(Object.keys(U).length>0)x(new E(U));let Z=await W.object(L).validate(J);if(!Z.valid){let X={};for(let G of Z.errors||[]){let N=G.field??"_";if(!X[N])X[N]=[];X[N].push(G.message)}throw new z(422,"Validation failed",{errors:X})}return Z.value??J}catch(Q){if(Q instanceof z)throw Q;throw new z(500,C(Q)||"An unexpected validation error occurred")}}async function CB(B,D){let J={},L={};for(let U in B)if(Object.prototype.hasOwnProperty.call(B,U)){let Q=B[U]?.rule;if(Q)J[U]=Q;let Z=B[U]?.message;for(let $ in Z){let X=`${U}.${$}`;L[X]=B[U]?.message[$]||""}}try{let Q=await W.object().shape(J).validate(D);if(!Q.valid)throw new z(422,"Validation failed",{errors:Q.errors});return Q}catch(U){if(U instanceof z)throw U;throw new z(500,C(U)||"An unexpected validation error occurred")}}function KB(B){return typeof B==="string"}function FB(B){return typeof B==="number"&&!Number.isNaN(B)}function qB(B){return typeof B==="boolean"}function MB(B){return typeof B==="object"&&B!==null&&!Array.isArray(B)}function OB(B){return Array.isArray(B)}function RB(B){return typeof B==="function"}function EB(B){return B===void 0}function xB(B){return B===null}function jB(B){return B===null||B===void 0}export{T as withConditionals,SB as validateFieldAsync,d as validateField,AB as validate,VB as unique,A as shouldApplyConditional,W as schema,O as reportError,YB as registerRule,I as objectWithContext,EB as isUndefined,KB as isString,WB as isObjectNotEmpty,MB as isObject,FB as isNumber,jB as isNullOrUndefined,xB as isNull,RB as isFunction,qB as isBoolean,OB as isArray,UB as getErrors,PB as getCustomRule,IB as exists,CB as customValidate,V as applyConditionals};
@@ -0,0 +1,37 @@
1
+ import type { ConditionalAPI } from './conditional';
2
+ import type { ValidationResult, Validator } from '@stacksjs/ts-validation';
3
+ /**
4
+ * Build a Stacks-flavored object validator that respects conditional
5
+ * rules on its child validators. Drop-in for `v.object(...)` in
6
+ * `@stacksjs/ts-validation` — same call shape, broader behavior.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * objectWithContext({
11
+ * payment_method: schema.enum(['card', 'bank']),
12
+ * card_token: schema.string().when('payment_method', 'card', s => s.required()),
13
+ * bank_account: schema.string().when('payment_method', 'bank', s => s.required()),
14
+ * username: schema.string().sometimes().minLength(3),
15
+ * }).validate(input)
16
+ * ```
17
+ */
18
+ export declare function objectWithContext<T extends Record<string, Validator<any>>>(initialShape?: T): ObjectWithContextValidator<T> & ConditionalAPI<ObjectWithContextValidator<T>>;
19
+ /**
20
+ * Validator-shaped object returned by `schema.object()`. Mirrors the
21
+ * subset of ts-validation's `ObjectValidatorType` that Stacks code
22
+ * actually uses today (`validate`, `shape`, `optional`, `required`).
23
+ *
24
+ * Extra method:
25
+ * - `getShape()` — exposes the underlying shape map so OpenAPI export
26
+ * and other introspection passes can walk the per-field validators
27
+ * (and their `__conditionals` arrays).
28
+ */
29
+ export declare interface ObjectWithContextValidator<T extends Record<string, Validator<any>>> extends Validator<Record<string, unknown>> {
30
+ readonly name: 'object'
31
+ validate: (value: unknown) => ValidationResult
32
+ getShape: () => T
33
+ required: () => this
34
+ optional: () => this
35
+ isPartOfShape: boolean
36
+ shape: (shape: T) => ObjectWithContextValidator<T>
37
+ }
@@ -0,0 +1,7 @@
1
+ export declare function reportError(errors: MessageObject[]): void;
2
+ export declare function getErrors(): MessageObject[];
3
+ declare interface MessageObject {
4
+ message: string
5
+ value: string
6
+ field: string
7
+ }
@@ -0,0 +1,36 @@
1
+ import { file, FileValidator } from './file-validator';
2
+ import { objectWithContext } from './object-with-context';
3
+ import { withConditionals } from './conditional';
4
+ import type { ConditionalAPI } from './conditional';
5
+ import type { ObjectWithContextValidator } from './object-with-context';
6
+ import type { ValidationInstance } from '@stacksjs/ts-validation';
7
+ export type { FileLike } from './file-validator';
8
+ export type { ConditionalAPI, ConditionalRecord, ValidatorWithConditionals } from './conditional';
9
+ export type { ObjectWithContextValidator } from './object-with-context';
10
+ /**
11
+ * `Object.assign({}, v, { file })` would defeat the ts-validation
12
+ * proxy's late-binding. Wrapping with `new Proxy` keeps every existing
13
+ * `schema.<method>()` call going through the upstream proxy while only
14
+ * intercepting the slots we own (`file`, `object`, and the conditional
15
+ * mixin on primitive factories).
16
+ */
17
+ export declare const schema: SchemaWithFile;
18
+ /**
19
+ * Extended `ValidationInstance` that adds `schema.file()` on top of the
20
+ * ts-validation surface (stacksjs/stacks#1856). Upstream ts-validation
21
+ * has `string()`, `number()`, `enum()`, etc. but no `file()` validator;
22
+ * we layer one here without forking ts-validation so the rest of the
23
+ * surface keeps working unchanged.
24
+ *
25
+ * The runtime value is the ts-validation proxy with `file` patched in,
26
+ * `object` swapped for the context-aware variant (stacksjs/stacks#1890),
27
+ * and every primitive factory wrapped to attach `.when()` / `.sometimes()`
28
+ * to the returned validator.
29
+ */
30
+ export type SchemaWithFile = Omit<ValidationInstance, 'object'> & {
31
+ file: () => FileValidator
32
+ object: typeof objectWithContext
33
+ }
34
+ export { file, FileValidator } from './file-validator';
35
+ export { applyConditionals, shouldApplyConditional, withConditionals } from './conditional';
36
+ export { objectWithContext } from './object-with-context';
@@ -0,0 +1,78 @@
1
+ import type { Validator } from '@stacksjs/ts-validation';
2
+ export declare function isObjectNotEmpty(obj: object | undefined): boolean;
3
+ export declare function validateField(modelFile: string, params: RequestData): Promise<any>;
4
+ /**
5
+ * Register a custom validation rule that can be referenced by name.
6
+ */
7
+ export declare function registerRule(name: string, rule: AsyncValidator): void;
8
+ /**
9
+ * Get a registered custom rule by name.
10
+ */
11
+ export declare function getCustomRule(name: string): AsyncValidator | undefined;
12
+ /**
13
+ * Built-in async rule: checks that a value is unique in a database table.
14
+ */
15
+ export declare function unique(table: string, column: string, exceptId?: number): AsyncValidator;
16
+ /**
17
+ * Built-in async rule: checks that a value exists in a database table.
18
+ */
19
+ export declare function exists(table: string, column: string): AsyncValidator;
20
+ /**
21
+ * Validate fields with both sync rules (from model) and async rules.
22
+ * Runs sync validation first, then async rules, combining all errors.
23
+ */
24
+ export declare function validateFieldAsync(modelFile: string, params: RequestData, asyncRules?: Record<string, AsyncValidator>): Promise<any>;
25
+ /**
26
+ * Action-side validation shortcut. Returns the validated input typed as
27
+ * `T` so callers can drop the post-validate type-cast they used to need.
28
+ *
29
+ * Throws `HttpError(422, 'Validation failed', { errors })` on the first
30
+ * failing field — the router's error handler turns that into a JSON
31
+ * 422 response automatically.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * import { validate, schema } from '@stacksjs/validation'
36
+ *
37
+ * type Payload = { email: string; age: number }
38
+ *
39
+ * const data = await validate<Payload>(req, {
40
+ * email: schema.string().email(),
41
+ * age: schema.number().min(0),
42
+ * })
43
+ *
44
+ * // `data.email` is `string`, `data.age` is `number` — no cast needed.
45
+ * ```
46
+ */
47
+ export declare function validate<T = Record<string, unknown>>(request: RequestLike | Record<string, unknown>, rules: ValidationRules): Promise<T>;
48
+ export declare function customValidate(attributes: CustomAttributes, params: RequestData): Promise<any>;
49
+ declare interface RequestData {
50
+ [key: string]: any
51
+ }
52
+ declare interface ValidationField {
53
+ rule: ValidationType
54
+ message: Record<string, string>
55
+ }
56
+ declare interface CustomAttributes {
57
+ [key: string]: ValidationField
58
+ }
59
+ declare interface RequestLike {
60
+ all?: () => Record<string, unknown> | Promise<Record<string, unknown>>
61
+ jsonBody?: Record<string, unknown>
62
+ formBody?: Record<string, unknown>
63
+ query?: Record<string, unknown>
64
+ params?: Record<string, unknown>
65
+ url?: string
66
+ }
67
+ /**
68
+ * Async validation rule type.
69
+ * Return true if valid, or a string error message if invalid.
70
+ */
71
+ // eslint-disable-next-line pickier/no-unused-vars
72
+ export type AsyncValidator = (value: unknown, params: RequestData) => Promise<boolean | string>;
73
+ /**
74
+ * Shape of a validation rule set understood by `validate()`. Each key
75
+ * maps to either a raw `Validator<T>` (from `@stacksjs/ts-validation`)
76
+ * or a `{ rule, message }` object that mirrors the model-attribute form.
77
+ */
78
+ export type ValidationRules = Record<string, Validator<any> | { rule: Validator<any>, message?: string | Record<string, string> }>;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/validation",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.88",
5
+ "version": "0.70.90",
6
6
  "description": "The Stacks Validation ways.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -57,7 +57,7 @@
57
57
  },
58
58
  "devDependencies": {
59
59
  "better-dx": "^0.2.16",
60
- "@stacksjs/strings": "0.70.88",
61
- "@stacksjs/types": "0.70.88"
60
+ "@stacksjs/strings": "0.70.90",
61
+ "@stacksjs/types": "0.70.90"
62
62
  }
63
63
  }