@stacksjs/validation 0.75.112 → 0.75.113

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -73,3 +73,16 @@ booleans, non-finite values and unsafe integers. Optional text and numbers retur
73
73
  null; omitted booleans return undefined. Explicit false and zero are preserved.
74
74
  Model validation checks supplied fields against their declared rules; the action
75
75
  still selects writable fields and enforces required fields and relationships.
76
+
77
+ ## Reusable boundary and stored-value rules
78
+
79
+ `parseTextInput(value, field, required?, maxLength?)` rejects overlong text
80
+ rather than truncating it. `parseEnumInput(value, field, choices)` requires
81
+ exact string membership; it never stringifies an object or boolean.
82
+ `storedBoolean(value, fallback)` decodes SQL/form 0/1 and false/true; the
83
+ caller explicitly chooses the fallback for missing or corrupt stored data.
84
+ Partial model validation accepts attribute names and their snake-case column
85
+ spelling, including camel-case definitions.
86
+
87
+ `positiveIdOrNull(value)` is the nullable adapter for optional identifiers and
88
+ record serializers; it uses the same safe-integer and type rules as parsePositiveId.
package/dist/input.d.ts CHANGED
@@ -1,11 +1,17 @@
1
1
  export declare function readJsonObject(request: { json: () => Promise<unknown> }): Promise<Record<string, unknown>>;
2
2
  export declare function parsePositiveId(value: unknown): number;
3
- export declare function parseTextInput(value: unknown, field: string, required?: boolean): string | null;
3
+ export declare function parseTextInput(value: unknown, field: string, required?: boolean, maxLength?: number): string | null;
4
4
  export declare function parseNumberInput(value: unknown, field: string, min: number, max: number, integer?: boolean): number | null;
5
5
  /** Supports form booleans without treating arbitrary text or objects as consent. */
6
6
  export declare function parseBooleanInput(value: unknown, field: string): boolean | undefined;
7
7
  /** Validate supplied fields only, so partial updates use the model's own rules. */
8
8
  export declare function validateModelInput(model: InputValidatableModel, values: Record<string, unknown>): void;
9
+ /** Exact enum membership without turning objects or numbers into text. */
10
+ export declare function parseEnumInput<const T extends readonly string[]>(value: unknown, field: string, choices: T): T[number];
11
+ /** Decode SQL/form booleans. The caller explicitly chooses the fallback for absent or corrupt data. */
12
+ export declare function storedBoolean(value: unknown, fallback: boolean): boolean;
13
+ /** The nullable adapter for record serializers and optional identifiers. */
14
+ export declare function positiveIdOrNull(value: unknown): number | null;
9
15
  export declare interface InputValidatableModel {
10
16
  getDefinition: () => { attributes?: Record<string, { validation?: { rule?: { validate(value: unknown): { valid: boolean } } } }> }
11
17
  }
package/dist/input.js CHANGED
@@ -1 +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("_"," ")}`)}}
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,maxLength){if(value==null&&!required)return null;if(typeof value!=="string")throw new InputValidationError(`${field} must be text`);const text=value.trim();if(maxLength!==void 0&&text.length>maxLength)throw new InputValidationError(`${field} is too long`);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){const attributes=model.getDefinition().attributes??{};for(const[field,value]of Object.entries(values)){const attribute=Object.prototype.hasOwnProperty.call(attributes,field)?field:Object.keys(attributes).find((key)=>key===field||key.replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/([A-Z])([A-Z][a-z])/g,"$1_$2").toLowerCase()===field),rule=attribute?attributes[attribute]?.validation?.rule:void 0;if(rule&&!rule.validate(value).valid)throw new InputValidationError(`Invalid ${field.replaceAll("_"," ")}`)}}export function parseEnumInput(value,field,choices){if(typeof value!=="string"||!choices.includes(value))throw new InputValidationError(`${field} must be ${choices.join(", ")}`);return value}export function storedBoolean(value,fallback){if(value==null)return fallback;try{return parseBooleanInput(value,"Stored value")??fallback}catch(error){if(error instanceof InputValidationError)return fallback;throw error}}export function positiveIdOrNull(value){try{return parsePositiveId(value)}catch(error){if(error instanceof InputValidationError)return null;throw error}}
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.112",
5
+ "version": "0.75.113",
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.112",
68
- "@stacksjs/path": "0.75.112",
69
- "@stacksjs/strings": "0.75.112",
67
+ "@stacksjs/error-handling": "0.75.113",
68
+ "@stacksjs/path": "0.75.113",
69
+ "@stacksjs/strings": "0.75.113",
70
70
  "@stacksjs/ts-validation": "^0.5.6",
71
- "@stacksjs/types": "0.75.112",
72
- "@stacksjs/utils": "0.75.112"
71
+ "@stacksjs/types": "0.75.113",
72
+ "@stacksjs/utils": "0.75.113"
73
73
  },
74
74
  "devDependencies": {
75
75
  "better-dx": "^0.2.24"
76
76
  },
77
77
  "peerDependencies": {
78
- "@stacksjs/database": "0.75.112"
78
+ "@stacksjs/database": "0.75.113"
79
79
  },
80
80
  "peerDependenciesMeta": {
81
81
  "@stacksjs/database": {