release: WRNexusJS 0.8.0
Quality / quality (ubuntu-latest) (push) Failing after 21s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-02 23:18:51 +05:30
parent 87507edf59
commit 586a6db8ff
625 changed files with 243608 additions and 11210 deletions
+100 -24
View File
@@ -24,6 +24,8 @@ export interface FieldDescriptor {
optional?: boolean;
/** Message used when a required field is empty. Defaults to "Required". */
requiredMessage?: string;
/** Message used when coercion to the declared type fails. */
typeMessage?: string;
label?: string;
/** Trim string input before validating. */
trim?: boolean;
@@ -131,7 +133,7 @@ export function checkField(
let value: unknown;
if (desc.type === "number") {
value = Number(pre);
if (Number.isNaN(value)) return { value, error: "Must be a number" };
if (Number.isNaN(value)) return { value, error: desc.typeMessage ?? "Must be a number" };
} else {
value = String(pre);
}
@@ -214,7 +216,9 @@ export abstract class FieldSchema {
}
}
export class StringSchema extends FieldSchema {
export class StringSchema<TValue extends string = string> extends FieldSchema {
/** Type-only marker used to preserve literal unions through schema inference. */
declare readonly __value: TValue;
readonly type = "string" as const;
private _trim = false;
email(message?: string): this {
@@ -237,9 +241,14 @@ export class StringSchema extends FieldSchema {
this.rules.push({ kind: "length", n, message });
return this;
}
oneOf(values: string[], message?: string): this {
this.rules.push({ kind: "oneOf", values, message });
return this;
oneOf<const TValues extends readonly string[]>(
values: TValues,
message?: string,
): StringSchema<TValues extends readonly [string, ...string[]] ? TValues[number] : TValue> {
this.rules.push({ kind: "oneOf", values: [...values], message });
return this as unknown as StringSchema<
TValues extends readonly [string, ...string[]] ? TValues[number] : TValue
>;
}
trim(): this {
this._trim = true;
@@ -254,7 +263,9 @@ export class StringSchema extends FieldSchema {
}
}
export class NumberSchema extends FieldSchema {
export class NumberSchema<TValue extends number = number> extends FieldSchema {
/** Type-only marker used to preserve numeric literal unions through schema inference. */
declare readonly __value: TValue;
readonly type = "number" as const;
integer(message?: string): this {
this.rules.push({ kind: "integer", message });
@@ -264,9 +275,14 @@ export class NumberSchema extends FieldSchema {
this.rules.push({ kind: "min", n: Number.MIN_VALUE, message: message ?? "Must be positive" });
return this;
}
oneOf(values: number[], message?: string): this {
this.rules.push({ kind: "oneOf", values, message });
return this;
oneOf<const TValues extends readonly number[]>(
values: TValues,
message?: string,
): NumberSchema<TValues extends readonly [number, ...number[]] ? TValues[number] : TValue> {
this.rules.push({ kind: "oneOf", values: [...values], message });
return this as unknown as NumberSchema<
TValues extends readonly [number, ...number[]] ? TValues[number] : TValue
>;
}
}
@@ -278,9 +294,32 @@ export class UnknownSchema extends FieldSchema {
readonly type = "unknown" as const;
}
export type AnyFieldSchema = StringSchema | NumberSchema | BooleanSchema | UnknownSchema;
export type AnyFieldSchema =
StringSchema<string> | NumberSchema<number> | BooleanSchema | UnknownSchema;
/** Infer the runtime value produced by a field schema. */
export type InferFieldValue<TField extends FieldSchema> =
TField extends StringSchema<infer TValue>
? TValue
: TField extends NumberSchema<infer TValue>
? TValue
: TField extends BooleanSchema
? boolean
: unknown;
/** Infer the validated object produced by a field map. */
export type InferObjectFields<TFields extends Record<string, FieldSchema>> = {
[K in keyof TFields]: InferFieldValue<TFields[K]>;
};
/** Infer the object value produced by an object schema. */
export type InferSchema<TSchema extends ObjectSchema> =
TSchema extends ObjectSchema<infer TValue> ? TValue : never;
export class ObjectSchema<TValue extends object = Record<string, unknown>> {
/** Type-only marker used by helper functions to infer validated output. */
declare readonly __output: TValue;
export class ObjectSchema {
constructor(private readonly fields: Record<string, FieldSchema>) {}
/** Return a defensive copy of the schema fields. */
@@ -289,17 +328,19 @@ export class ObjectSchema {
}
/** Create a new schema with fields added or replaced. The original is unchanged. */
extend(fields: Record<string, FieldSchema>): ObjectSchema {
extend<TFields extends Record<string, FieldSchema>>(
fields: TFields,
): ObjectSchema<Omit<TValue, keyof TFields> & InferObjectFields<TFields>> {
return new ObjectSchema({ ...this.fields, ...fields });
}
/** Create a new schema containing fields from both schemas. */
merge(schema: ObjectSchema): ObjectSchema {
merge<TOther extends object>(schema: ObjectSchema<TOther>): ObjectSchema<TValue & TOther> {
return new ObjectSchema({ ...this.fields, ...schema.getFields() });
}
/** Validate an input object; returns coerced values + per-field errors. */
parse(input: unknown): ParseResult {
parse(input: unknown): ParseResult<TValue> {
const source = (input ?? {}) as Record<string, unknown>;
const value: Record<string, unknown> = {};
const errors: Record<string, string> = {};
@@ -323,7 +364,7 @@ export class ObjectSchema {
value[name] = finalValue;
}
}
return { ok: Object.keys(errors).length === 0, value, errors };
return { ok: Object.keys(errors).length === 0, value: value as TValue, errors };
}
describe(): SchemaDescriptor {
@@ -335,11 +376,12 @@ export class ObjectSchema {
/** The fluent schema builder. */
export const v = {
string: () => new StringSchema(),
number: () => new NumberSchema(),
string: () => new StringSchema<string>(),
number: () => new NumberSchema<number>(),
boolean: () => new BooleanSchema(),
unknown: () => new UnknownSchema(),
object: (fields: Record<string, FieldSchema>) => new ObjectSchema(fields),
object: <TFields extends Record<string, FieldSchema>>(fields: TFields) =>
new ObjectSchema<InferObjectFields<TFields>>(fields),
};
// --- Environment configuration --------------------------------------------
@@ -364,8 +406,8 @@ function readEnv(): Record<string, string | undefined> {
* PORT: v.number(),
* }));
*/
export function parseEnv<T = Record<string, unknown>>(
schema: ObjectSchema,
export function parseEnv<T extends object>(
schema: ObjectSchema<T>,
source: Record<string, string | undefined> = readEnv(),
): T {
const result = schema.parse(source);
@@ -373,7 +415,7 @@ export function parseEnv<T = Record<string, unknown>>(
const lines = Object.entries(result.errors).map(([name, message]) => `${name}: ${message}`);
throw new Error(`Invalid environment variables:\n${lines.join("\n")}`);
}
return result.value as T;
return result.value;
}
// --- API helpers -----------------------------------------------------------
@@ -387,14 +429,14 @@ export function invalid(errors: Record<string, string>): Response {
* Parse a request's JSON body against a schema. On failure returns
* `{ ok: false, response }` (a ready 400); on success `{ ok: true, value }`.
*/
export async function parseBody<T = Record<string, unknown>>(
schema: ObjectSchema,
export async function parseBody<T extends object>(
schema: ObjectSchema<T>,
req: Request,
): Promise<{ ok: true; value: T } | { ok: false; response: Response }> {
const body = await readBody(req);
const result = schema.parse(body);
if (!result.ok) return { ok: false, response: invalid(result.errors) };
return { ok: true, value: result.value as T };
return { ok: true, value: result.value };
}
/** Read a request body as an object from JSON, form-urlencoded, or multipart. */
@@ -428,3 +470,37 @@ export {
mergeValidationResults,
} from "./advanced.ts";
export type { AsyncValidationContext, AsyncRefinement, OpenApiSchema } from "./advanced.ts";
export {
ValidationError,
parseOrThrow,
validationResponse,
firstValidationError,
validationSummary,
schemaFieldNames,
} from "./helpers.ts";
export { validationPlugin, validationComponentsDir } from "./plugin.ts";
export type { ValidationPluginOptions } from "./plugin.ts";
export {
toJsonSchema,
openApiRequestBody,
localizeDescriptor,
parseDescriptor,
} from "./interop.ts";
export type {
JsonSchemaDocument,
ValidationMessageKey,
ValidationMessageTranslator,
} from "./interop.ts";
export {
ContractRegistry,
checkContractCompatibility,
defineContract,
defineEvent,
} from "./contracts.ts";
export type {
ContractDefinition,
ContractIssue,
ContractKind,
ContractRecord,
ContractSnapshot,
} from "./contracts.ts";