@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.
- package/dist/conditional.d.ts +59 -0
- package/dist/file-validator.d.ts +65 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +2 -0
- package/dist/object-with-context.d.ts +37 -0
- package/dist/reporter.d.ts +7 -0
- package/dist/schema.d.ts +36 -0
- package/dist/validator.d.ts +78 -0
- package/package.json +3 -3
|
@@ -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
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/schema.d.ts
ADDED
|
@@ -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.
|
|
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.
|
|
61
|
-
"@stacksjs/types": "0.70.
|
|
60
|
+
"@stacksjs/strings": "0.70.90",
|
|
61
|
+
"@stacksjs/types": "0.70.90"
|
|
62
62
|
}
|
|
63
63
|
}
|