@stone-js/validation 0.8.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright © 2026 Stone Foundation
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # Stone.js · Validation
2
+
3
+ [![npm](https://img.shields.io/npm/v/@stone-js/validation)](https://www.npmjs.com/package/@stone-js/validation)
4
+ [![CI](https://github.com/stone-foundation/stone-js-framework/actions/workflows/ci.yml/badge.svg)](https://github.com/stone-foundation/stone-js-framework/actions/workflows/ci.yml)
5
+ [![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=stone-foundation_stone-js-framework&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=stone-foundation_stone-js-framework)
6
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
+ [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org)
8
+
9
+ > Framework-agnostic input validation for Stone.js. Define a schema once (Zod, Valibot, ArkType — anything Standard Schema) and validate it identically on the backend and the frontend.
10
+
11
+ Part of **[Stone.js](https://stonejs.dev)**, the reference implementation of the
12
+ [Continuum Architecture](https://evens-stone.github.io/continuum-manifesto/manifesto): write your
13
+ domain once, and the context (runtime, protocol, caller) applies to it at run time.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm i @stone-js/validation
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```ts
24
+ import { z } from 'zod'
25
+ import { validate } from '@stone-js/validation'
26
+ import { EventHandler, Post } from '@stone-js/router'
27
+
28
+ export const NewTask = z.object({ title: z.string().min(1).max(120) })
29
+
30
+ @EventHandler('/tasks')
31
+ export class TaskController {
32
+ // Rejects a malformed body with 422 before the handler runs. Same schema validates the form.
33
+ @Post('/', { middleware: [validate({ body: NewTask })] })
34
+ create (event) { return event.get('body') }
35
+ }
36
+ ```
37
+
38
+ ## Documentation
39
+
40
+ Full documentation: **[stonejs.dev/docs/extensions/validation](https://stonejs.dev/docs/extensions/validation)**.
41
+
42
+ ## License
43
+
44
+ [MIT](https://opensource.org/licenses/MIT) © Evens Pierre ("Mr. Stone") and the Stone.js contributors.
@@ -0,0 +1,16 @@
1
+ import { IContainer, IServiceProvider, Promiseable } from '@stone-js/core';
2
+ /**
3
+ * Registers the {@link Validator} service (singleton) in the container, aliased as
4
+ * `validator`/`Validator`, so middleware, handlers and services can resolve it.
5
+ */
6
+ export declare class ValidationServiceProvider implements IServiceProvider {
7
+ private readonly container;
8
+ /**
9
+ * @param container - The service container.
10
+ */
11
+ constructor(container: IContainer);
12
+ /**
13
+ * Register the validation service.
14
+ */
15
+ register(): Promiseable<void>;
16
+ }
@@ -0,0 +1,43 @@
1
+ import { IValidator, SchemaInput, ValidationResult } from './declarations';
2
+ /**
3
+ * The validation service.
4
+ *
5
+ * Platform-agnostic: it validates any value against any supported schema (Zod, Valibot, ArkType
6
+ * via Standard Schema, or a native Stone.js schema) and knows nothing about HTTP/CLI/browser.
7
+ * Register it in the container (see `ValidationServiceProvider`) and resolve it as `validator`,
8
+ * or use the same schema directly on the frontend — one schema, both sides.
9
+ */
10
+ export declare class Validator implements IValidator {
11
+ /**
12
+ * Factory.
13
+ *
14
+ * @returns A new Validator instance.
15
+ */
16
+ static create(): Validator;
17
+ /**
18
+ * Validate `data` against `schema`, returning a normalised result (never throws for validation
19
+ * failures — inspect `success`).
20
+ *
21
+ * @param schema - The schema.
22
+ * @param data - The value to validate.
23
+ * @returns The validation result.
24
+ */
25
+ validate<T>(schema: SchemaInput<T>, data: unknown): ValidationResult<T>;
26
+ /**
27
+ * Validate `data` and return the parsed value, or throw a {@link ValidationError} on failure.
28
+ *
29
+ * @param schema - The schema.
30
+ * @param data - The value to validate.
31
+ * @returns The parsed value.
32
+ * @throws {ValidationError} When validation fails.
33
+ */
34
+ assert<T>(schema: SchemaInput<T>, data: unknown): T;
35
+ /**
36
+ * Whether `data` satisfies `schema`.
37
+ *
38
+ * @param schema - The schema.
39
+ * @param data - The value to validate.
40
+ * @returns True when valid.
41
+ */
42
+ isValid<T>(schema: SchemaInput<T>, data: unknown): boolean;
43
+ }
@@ -0,0 +1,17 @@
1
+ import { ValidationSchema, StandardSchemaV1 } from '../declarations';
2
+ /**
3
+ * Adapts a [Standard Schema](https://standardschema.dev) (Zod 3.24+, Valibot, ArkType, …) to the
4
+ * Stone.js {@link ValidationSchema} contract. Only the synchronous path is supported here; an
5
+ * async schema throws a clear {@link ValidationError} so the misuse is obvious.
6
+ *
7
+ * @param schema - The Standard Schema.
8
+ * @returns A Stone.js validation schema.
9
+ */
10
+ export declare function fromStandard<T>(schema: StandardSchemaV1<T>): ValidationSchema<T>;
11
+ /**
12
+ * Whether a value implements the Standard Schema v1 contract.
13
+ *
14
+ * @param value - The value to test.
15
+ * @returns True when it exposes a `~standard` v1 entry.
16
+ */
17
+ export declare function isStandardSchema(value: unknown): value is StandardSchemaV1;
@@ -0,0 +1,17 @@
1
+ import { ValidationSchema, ZodLikeSchema } from '../declarations';
2
+ /**
3
+ * Adapts a Zod-style schema (anything exposing a synchronous `safeParse`) to the Stone.js
4
+ * {@link ValidationSchema} contract. Structural — never imports Zod, so it works with any
5
+ * compatible engine and keeps the module dependency-free.
6
+ *
7
+ * @param schema - The Zod-like schema.
8
+ * @returns A Stone.js validation schema.
9
+ */
10
+ export declare function fromZod<T>(schema: ZodLikeSchema<T>): ValidationSchema<T>;
11
+ /**
12
+ * Whether a value looks like a Zod-style schema.
13
+ *
14
+ * @param value - The value to test.
15
+ * @returns True when it exposes a `safeParse` function.
16
+ */
17
+ export declare function isZodLike(value: unknown): value is ZodLikeSchema;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * A single validation problem, normalised across engines.
3
+ */
4
+ export interface ValidationIssue {
5
+ /** Property path to the offending value (e.g. `['user', 'email']`). */
6
+ path: Array<string | number>;
7
+ /** Human-readable message. */
8
+ message: string;
9
+ /** Optional engine/rule code (e.g. `too_small`). */
10
+ code?: string;
11
+ }
12
+ /**
13
+ * The outcome of validating a value against a schema. Never throws — inspect `success`.
14
+ */
15
+ export type ValidationResult<T> = {
16
+ success: true;
17
+ value: T;
18
+ issues?: undefined;
19
+ } | {
20
+ success: false;
21
+ value?: undefined;
22
+ issues: ValidationIssue[];
23
+ };
24
+ /**
25
+ * The engine-agnostic schema contract every validator speaks.
26
+ *
27
+ * Any object exposing a `validate(data)` method that returns a {@link ValidationResult} is a
28
+ * valid Stone.js schema. Zod, Valibot and ArkType schemas are adapted to this shape (they all
29
+ * implement the Standard Schema spec, or expose `safeParse`), so you write the schema once and
30
+ * use it identically on the backend and the frontend.
31
+ */
32
+ export interface ValidationSchema<T = unknown> {
33
+ /** Validate a value, returning a normalised result. */
34
+ validate: (data: unknown) => ValidationResult<T>;
35
+ }
36
+ /**
37
+ * Minimal shape of the [Standard Schema](https://standardschema.dev) v1 contract — implemented
38
+ * by Zod 3.24+, Valibot, ArkType and others. Only the synchronous path is consumed here.
39
+ */
40
+ export interface StandardSchemaV1<Output = unknown> {
41
+ readonly '~standard': {
42
+ readonly version: 1;
43
+ readonly vendor: string;
44
+ readonly validate: (value: unknown) => StandardResult<Output> | Promise<StandardResult<Output>>;
45
+ };
46
+ }
47
+ /** A Standard Schema validation result. */
48
+ export interface StandardResult<Output> {
49
+ readonly value?: Output;
50
+ readonly issues?: ReadonlyArray<{
51
+ readonly message: string;
52
+ readonly path?: ReadonlyArray<PropertyKey | {
53
+ readonly key: PropertyKey;
54
+ }>;
55
+ }>;
56
+ }
57
+ /**
58
+ * Minimal shape of a Zod-style schema (a `safeParse` method). Kept structural so `@stone-js/validation`
59
+ * never has to depend on Zod at runtime.
60
+ */
61
+ export interface ZodLikeSchema<T = unknown> {
62
+ safeParse: (data: unknown) => ZodSafeParseResult<T>;
63
+ }
64
+ /** A Zod-style `safeParse` result. */
65
+ export type ZodSafeParseResult<T> = {
66
+ success: true;
67
+ data: T;
68
+ } | {
69
+ success: false;
70
+ error: {
71
+ issues: ReadonlyArray<{
72
+ message: string;
73
+ path: readonly PropertyKey[];
74
+ code?: string;
75
+ }>;
76
+ };
77
+ };
78
+ /**
79
+ * Anything that can be resolved into a {@link ValidationSchema}: a native Stone.js schema, a
80
+ * Standard Schema, or a Zod-like schema.
81
+ */
82
+ export type SchemaInput<T = unknown> = ValidationSchema<T> | StandardSchemaV1<T> | ZodLikeSchema<T>;
83
+ /**
84
+ * The telemetry-free validation service contract.
85
+ */
86
+ export interface IValidator {
87
+ /** Validate `data` against `schema`, returning a normalised result (never throws). */
88
+ validate: <T>(schema: SchemaInput<T>, data: unknown) => ValidationResult<T>;
89
+ /** Validate `data` and return the parsed value, or throw a `ValidationError` on failure. */
90
+ assert: <T>(schema: SchemaInput<T>, data: unknown) => T;
91
+ /** Whether `data` satisfies `schema`. */
92
+ isValid: <T>(schema: SchemaInput<T>, data: unknown) => boolean;
93
+ }
94
+ /**
95
+ * Validation configuration (`stone.validation.*`).
96
+ */
97
+ export interface ValidationOptions {
98
+ /** Whether to strip unknown keys is left to the schema; reserved for future options. */
99
+ reserved?: never;
100
+ }
@@ -0,0 +1,32 @@
1
+ import { ValidationIssue } from '../declarations';
2
+ import { IntegrationError } from '@stone-js/core';
3
+ import type { ErrorOptions } from '@stone-js/core';
4
+ /**
5
+ * Options for a {@link ValidationError}.
6
+ */
7
+ export interface ValidationErrorOptions extends ErrorOptions {
8
+ /** The normalised validation issues. */
9
+ issues: ValidationIssue[];
10
+ }
11
+ /**
12
+ * Thrown when a value fails validation.
13
+ *
14
+ * It carries the normalised {@link ValidationIssue}s so any layer can render them (an HTTP error
15
+ * handler into `422` + problem+json, a CLI into a table, a form into field errors). The error
16
+ * itself stays platform-agnostic — it knows nothing about HTTP.
17
+ */
18
+ export declare class ValidationError extends IntegrationError {
19
+ /** The normalised validation issues. */
20
+ readonly issues: ValidationIssue[];
21
+ /**
22
+ * @param message - The error message.
23
+ * @param options - The error options, including the issues.
24
+ */
25
+ constructor(message: string, options: ValidationErrorOptions);
26
+ /**
27
+ * A plain, serialisable representation (suitable as a problem+json `errors` payload).
28
+ *
29
+ * @returns The issues keyed by dotted path.
30
+ */
31
+ toIssuesRecord(): Record<string, string[]>;
32
+ }
@@ -0,0 +1,10 @@
1
+ export * from './ValidationServiceProvider';
2
+ export * from './Validator';
3
+ export * from './adapters/standardSchema';
4
+ export * from './adapters/zod';
5
+ export * from './declarations';
6
+ export * from './errors/ValidationError';
7
+ export * from './middleware/validate';
8
+ export * from './options/ValidationBlueprint';
9
+ export * from './schema';
10
+ export * from './validateEvent';
package/dist/index.js ADDED
@@ -0,0 +1,286 @@
1
+ import { IntegrationError } from '@stone-js/core';
2
+
3
+ /**
4
+ * Adapts a Zod-style schema (anything exposing a synchronous `safeParse`) to the Stone.js
5
+ * {@link ValidationSchema} contract. Structural — never imports Zod, so it works with any
6
+ * compatible engine and keeps the module dependency-free.
7
+ *
8
+ * @param schema - The Zod-like schema.
9
+ * @returns A Stone.js validation schema.
10
+ */
11
+ function fromZod(schema) {
12
+ return {
13
+ validate: (data) => {
14
+ const result = schema.safeParse(data);
15
+ if (result.success) {
16
+ return { success: true, value: result.data };
17
+ }
18
+ const issues = result.error.issues.map((issue) => ({
19
+ path: issue.path.map((segment) => typeof segment === 'symbol' ? String(segment) : segment),
20
+ message: issue.message,
21
+ code: issue.code
22
+ }));
23
+ return { success: false, issues };
24
+ }
25
+ };
26
+ }
27
+ /**
28
+ * Whether a value looks like a Zod-style schema.
29
+ *
30
+ * @param value - The value to test.
31
+ * @returns True when it exposes a `safeParse` function.
32
+ */
33
+ function isZodLike(value) {
34
+ return typeof value?.safeParse === 'function';
35
+ }
36
+
37
+ /**
38
+ * Thrown when a value fails validation.
39
+ *
40
+ * It carries the normalised {@link ValidationIssue}s so any layer can render them (an HTTP error
41
+ * handler into `422` + problem+json, a CLI into a table, a form into field errors). The error
42
+ * itself stays platform-agnostic — it knows nothing about HTTP.
43
+ */
44
+ class ValidationError extends IntegrationError {
45
+ /** The normalised validation issues. */
46
+ issues;
47
+ /**
48
+ * @param message - The error message.
49
+ * @param options - The error options, including the issues.
50
+ */
51
+ constructor(message, options) {
52
+ super(message, options);
53
+ this.name = 'ValidationError';
54
+ this.issues = options.issues;
55
+ }
56
+ /**
57
+ * A plain, serialisable representation (suitable as a problem+json `errors` payload).
58
+ *
59
+ * @returns The issues keyed by dotted path.
60
+ */
61
+ toIssuesRecord() {
62
+ return this.issues.reduce((acc, issue) => {
63
+ const key = issue.path.length > 0 ? issue.path.join('.') : '_';
64
+ acc[key] = acc[key] ?? [];
65
+ acc[key].push(issue.message);
66
+ return acc;
67
+ }, {});
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Adapts a [Standard Schema](https://standardschema.dev) (Zod 3.24+, Valibot, ArkType, …) to the
73
+ * Stone.js {@link ValidationSchema} contract. Only the synchronous path is supported here; an
74
+ * async schema throws a clear {@link ValidationError} so the misuse is obvious.
75
+ *
76
+ * @param schema - The Standard Schema.
77
+ * @returns A Stone.js validation schema.
78
+ */
79
+ function fromStandard(schema) {
80
+ return {
81
+ validate: (data) => {
82
+ const result = schema['~standard'].validate(data);
83
+ if (result instanceof Promise) {
84
+ throw new ValidationError('Asynchronous Standard Schemas are not supported by the synchronous validator.', { issues: [] });
85
+ }
86
+ if (result.issues === undefined) {
87
+ return { success: true, value: result.value };
88
+ }
89
+ const issues = result.issues.map((issue) => ({
90
+ path: (issue.path ?? []).map((segment) => {
91
+ const key = typeof segment === 'object' ? segment.key : segment;
92
+ return typeof key === 'symbol' ? String(key) : key;
93
+ }),
94
+ message: issue.message
95
+ }));
96
+ return { success: false, issues };
97
+ }
98
+ };
99
+ }
100
+ /**
101
+ * Whether a value implements the Standard Schema v1 contract.
102
+ *
103
+ * @param value - The value to test.
104
+ * @returns True when it exposes a `~standard` v1 entry.
105
+ */
106
+ function isStandardSchema(value) {
107
+ const std = value?.['~standard'];
108
+ return std !== undefined && std.version === 1 && typeof std.validate === 'function';
109
+ }
110
+
111
+ /**
112
+ * Normalises any supported schema input into a Stone.js {@link ValidationSchema}.
113
+ *
114
+ * Resolution order: a Standard Schema (`~standard`) is preferred (canonical, covers Zod 3.24+,
115
+ * Valibot, ArkType), then a Zod-like `safeParse`, then a native Stone.js schema (`validate`).
116
+ *
117
+ * @param input - The schema to resolve.
118
+ * @returns A Stone.js validation schema.
119
+ * @throws {ValidationError} When the input is not a recognisable schema.
120
+ */
121
+ function resolveSchema(input) {
122
+ if (isStandardSchema(input)) {
123
+ return fromStandard(input);
124
+ }
125
+ if (isZodLike(input)) {
126
+ return fromZod(input);
127
+ }
128
+ if (isNativeSchema(input)) {
129
+ return input;
130
+ }
131
+ throw new ValidationError('Unrecognised validation schema: expected a Standard Schema, a Zod-like schema, or a Stone.js schema.', { issues: [] });
132
+ }
133
+ /**
134
+ * Whether a value is already a native Stone.js {@link ValidationSchema}.
135
+ *
136
+ * @param value - The value to test.
137
+ * @returns True when it exposes a `validate` function.
138
+ */
139
+ function isNativeSchema(value) {
140
+ return typeof value?.validate === 'function';
141
+ }
142
+
143
+ /**
144
+ * The validation service.
145
+ *
146
+ * Platform-agnostic: it validates any value against any supported schema (Zod, Valibot, ArkType
147
+ * via Standard Schema, or a native Stone.js schema) and knows nothing about HTTP/CLI/browser.
148
+ * Register it in the container (see `ValidationServiceProvider`) and resolve it as `validator`,
149
+ * or use the same schema directly on the frontend — one schema, both sides.
150
+ */
151
+ class Validator {
152
+ /**
153
+ * Factory.
154
+ *
155
+ * @returns A new Validator instance.
156
+ */
157
+ static create() {
158
+ return new this();
159
+ }
160
+ /**
161
+ * Validate `data` against `schema`, returning a normalised result (never throws for validation
162
+ * failures — inspect `success`).
163
+ *
164
+ * @param schema - The schema.
165
+ * @param data - The value to validate.
166
+ * @returns The validation result.
167
+ */
168
+ validate(schema, data) {
169
+ return resolveSchema(schema).validate(data);
170
+ }
171
+ /**
172
+ * Validate `data` and return the parsed value, or throw a {@link ValidationError} on failure.
173
+ *
174
+ * @param schema - The schema.
175
+ * @param data - The value to validate.
176
+ * @returns The parsed value.
177
+ * @throws {ValidationError} When validation fails.
178
+ */
179
+ assert(schema, data) {
180
+ const result = this.validate(schema, data);
181
+ if (!result.success) {
182
+ throw new ValidationError('The given data failed validation.', { issues: result.issues });
183
+ }
184
+ return result.value;
185
+ }
186
+ /**
187
+ * Whether `data` satisfies `schema`.
188
+ *
189
+ * @param schema - The schema.
190
+ * @param data - The value to validate.
191
+ * @returns True when valid.
192
+ */
193
+ isValid(schema, data) {
194
+ return this.validate(schema, data).success;
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Registers the {@link Validator} service (singleton) in the container, aliased as
200
+ * `validator`/`Validator`, so middleware, handlers and services can resolve it.
201
+ */
202
+ class ValidationServiceProvider {
203
+ container;
204
+ /**
205
+ * @param container - The service container.
206
+ */
207
+ constructor(container) {
208
+ this.container = container;
209
+ }
210
+ /**
211
+ * Register the validation service.
212
+ */
213
+ register() {
214
+ this.container
215
+ .singletonIf(Validator, () => Validator.create())
216
+ .alias(Validator, ['validator', 'Validator']);
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Validates several event inputs at once against their schemas.
222
+ *
223
+ * For each `[key, schema]` it validates `event.get(key)` and collects every issue (each issue's
224
+ * path is prefixed with its key). If any input fails, it throws a single {@link ValidationError}
225
+ * carrying all the issues — so the caller sees the full picture, not just the first failure.
226
+ *
227
+ * Platform-agnostic: the event only needs a `get(key)` method, so it works for HTTP, CLI, browser
228
+ * or any other context.
229
+ *
230
+ * @param event - The incoming event (anything with `get`).
231
+ * @param rules - The validation rules.
232
+ * @param validator - The validator to use (defaults to a fresh stateless one).
233
+ * @throws {ValidationError} When any input fails validation.
234
+ */
235
+ function validateEvent(event, rules, validator = Validator.create()) {
236
+ const issues = [];
237
+ for (const [key, schema] of Object.entries(rules)) {
238
+ const result = validator.validate(schema, event.get(key));
239
+ if (!result.success) {
240
+ for (const issue of result.issues) {
241
+ issues.push({ ...issue, path: [key, ...issue.path] });
242
+ }
243
+ }
244
+ }
245
+ if (issues.length > 0) {
246
+ throw new ValidationError('The given data failed validation.', { issues });
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Builds a route middleware that validates the event's inputs before the handler runs.
252
+ *
253
+ * Attach it to any route's `middleware` — declarative
254
+ * (`@Post('/users', { middleware: [validate({ name: NameSchema })] })`) or imperative
255
+ * (route definition `middleware: [validate({ ... })]`). On failure it throws a
256
+ * `ValidationError` (map it to `422` + problem+json in your HTTP error handler); on success the
257
+ * handler runs untouched. The same schema works on the frontend via the `Validator` service —
258
+ * one schema, both sides.
259
+ *
260
+ * @param rules - The validation rules (event key → schema).
261
+ * @returns A functional middleware.
262
+ */
263
+ function validate(rules) {
264
+ const validator = Validator.create();
265
+ return async (event, next) => {
266
+ validateEvent(event, rules, validator);
267
+ return await next(event);
268
+ };
269
+ }
270
+
271
+ /**
272
+ * Opt-in blueprint: import and register it to enable validation.
273
+ *
274
+ * It contributes the validation service provider. `stone.providers` is an array, so this merges
275
+ * with the rest of the app rather than replacing anything.
276
+ */
277
+ const validationBlueprint = {
278
+ stone: {
279
+ validation: {},
280
+ providers: [
281
+ ValidationServiceProvider
282
+ ]
283
+ }
284
+ };
285
+
286
+ export { ValidationError, ValidationServiceProvider, Validator, fromStandard, fromZod, isNativeSchema, isStandardSchema, isZodLike, resolveSchema, validate, validateEvent, validationBlueprint };
@@ -0,0 +1,16 @@
1
+ import { ValidationRules } from '../validateEvent';
2
+ import { IncomingEvent, OutgoingResponse, FunctionalMiddleware } from '@stone-js/core';
3
+ /**
4
+ * Builds a route middleware that validates the event's inputs before the handler runs.
5
+ *
6
+ * Attach it to any route's `middleware` — declarative
7
+ * (`@Post('/users', { middleware: [validate({ name: NameSchema })] })`) or imperative
8
+ * (route definition `middleware: [validate({ ... })]`). On failure it throws a
9
+ * `ValidationError` (map it to `422` + problem+json in your HTTP error handler); on success the
10
+ * handler runs untouched. The same schema works on the frontend via the `Validator` service —
11
+ * one schema, both sides.
12
+ *
13
+ * @param rules - The validation rules (event key → schema).
14
+ * @returns A functional middleware.
15
+ */
16
+ export declare function validate(rules: ValidationRules): FunctionalMiddleware<IncomingEvent, OutgoingResponse>;
@@ -0,0 +1,26 @@
1
+ import { ValidationOptions } from '../declarations';
2
+ import { AppConfig, StoneBlueprint } from '@stone-js/core';
3
+ /**
4
+ * Validation configuration bucket (`stone.validation`).
5
+ */
6
+ export interface ValidationConfig extends ValidationOptions {
7
+ }
8
+ /**
9
+ * Application config augmented with the validation bucket.
10
+ */
11
+ export interface ValidationAppConfig extends Partial<AppConfig> {
12
+ validation: ValidationConfig;
13
+ }
14
+ /**
15
+ * Blueprint for the validation module.
16
+ */
17
+ export interface ValidationBlueprint extends StoneBlueprint {
18
+ stone: ValidationAppConfig;
19
+ }
20
+ /**
21
+ * Opt-in blueprint: import and register it to enable validation.
22
+ *
23
+ * It contributes the validation service provider. `stone.providers` is an array, so this merges
24
+ * with the rest of the app rather than replacing anything.
25
+ */
26
+ export declare const validationBlueprint: ValidationBlueprint;
@@ -0,0 +1,19 @@
1
+ import { SchemaInput, ValidationSchema } from './declarations';
2
+ /**
3
+ * Normalises any supported schema input into a Stone.js {@link ValidationSchema}.
4
+ *
5
+ * Resolution order: a Standard Schema (`~standard`) is preferred (canonical, covers Zod 3.24+,
6
+ * Valibot, ArkType), then a Zod-like `safeParse`, then a native Stone.js schema (`validate`).
7
+ *
8
+ * @param input - The schema to resolve.
9
+ * @returns A Stone.js validation schema.
10
+ * @throws {ValidationError} When the input is not a recognisable schema.
11
+ */
12
+ export declare function resolveSchema<T>(input: SchemaInput<T>): ValidationSchema<T>;
13
+ /**
14
+ * Whether a value is already a native Stone.js {@link ValidationSchema}.
15
+ *
16
+ * @param value - The value to test.
17
+ * @returns True when it exposes a `validate` function.
18
+ */
19
+ export declare function isNativeSchema<T>(value: unknown): value is ValidationSchema<T>;
@@ -0,0 +1,30 @@
1
+ import { IValidator, SchemaInput } from './declarations';
2
+ /**
3
+ * A map of event keys to the schema that validates each one.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * { email: EmailSchema, age: AgeSchema }
8
+ * ```
9
+ */
10
+ export type ValidationRules = Record<string, SchemaInput>;
11
+ /** The minimal event shape the validator reads from — platform-agnostic. */
12
+ export interface ReadableEvent {
13
+ get: <T>(key: string) => T | undefined;
14
+ }
15
+ /**
16
+ * Validates several event inputs at once against their schemas.
17
+ *
18
+ * For each `[key, schema]` it validates `event.get(key)` and collects every issue (each issue's
19
+ * path is prefixed with its key). If any input fails, it throws a single {@link ValidationError}
20
+ * carrying all the issues — so the caller sees the full picture, not just the first failure.
21
+ *
22
+ * Platform-agnostic: the event only needs a `get(key)` method, so it works for HTTP, CLI, browser
23
+ * or any other context.
24
+ *
25
+ * @param event - The incoming event (anything with `get`).
26
+ * @param rules - The validation rules.
27
+ * @param validator - The validator to use (defaults to a fresh stateless one).
28
+ * @throws {ValidationError} When any input fails validation.
29
+ */
30
+ export declare function validateEvent(event: ReadableEvent, rules: ValidationRules, validator?: IValidator): void;
package/package.json ADDED
@@ -0,0 +1,90 @@
1
+ {
2
+ "name": "@stone-js/validation",
3
+ "version": "0.8.0",
4
+ "description": "Framework-agnostic input validation for Stone.js. Define a schema once (Zod, Valibot, ArkType — anything Standard Schema) and validate it identically on the backend and the frontend.",
5
+ "author": "Mr. Stone <evensstone@gmail.com>",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/stone-foundation/stone-js-framework.git",
10
+ "directory": "stone-js-validation"
11
+ },
12
+ "homepage": "https://stonejs.dev",
13
+ "bugs": {
14
+ "url": "https://github.com/stone-foundation/stone-js-framework/issues"
15
+ },
16
+ "keywords": [
17
+ "StoneJS",
18
+ "validation",
19
+ "schema",
20
+ "zod",
21
+ "valibot",
22
+ "arktype",
23
+ "standard-schema",
24
+ "isomorphic",
25
+ "agnostic"
26
+ ],
27
+ "files": [
28
+ "/dist"
29
+ ],
30
+ "type": "module",
31
+ "sideEffects": false,
32
+ "types": "./dist/index.d.ts",
33
+ "exports": {
34
+ ".": {
35
+ "types": "./dist/index.d.ts",
36
+ "default": "./dist/index.js"
37
+ }
38
+ },
39
+ "engines": {
40
+ "node": ">=18.17.0"
41
+ },
42
+ "peerDependencies": {
43
+ "@stone-js/core": "0.8.0"
44
+ },
45
+ "devDependencies": {
46
+ "@commitlint/cli": "^19.8.1",
47
+ "@commitlint/config-conventional": "^19.8.1",
48
+ "@rollup/plugin-commonjs": "^28.0.6",
49
+ "@rollup/plugin-multi-entry": "^6.0.1",
50
+ "@rollup/plugin-node-resolve": "^16.0.1",
51
+ "@rollup/plugin-typescript": "^12.1.4",
52
+ "@types/node": "^24.0.7",
53
+ "@vitest/coverage-v8": "^3.2.4",
54
+ "husky": "^9.1.7",
55
+ "rimraf": "^6.0.1",
56
+ "rollup": "^4.44.1",
57
+ "rollup-plugin-node-externals": "^8.0.1",
58
+ "ts-standard": "^12.0.2",
59
+ "tslib": "^2.8.1",
60
+ "typedoc": "^0.28.6",
61
+ "typedoc-plugin-markdown": "^4.7.0",
62
+ "typescript": "^5.6.3",
63
+ "vitest": "^3.2.4",
64
+ "zod": "^3.24.1",
65
+ "@stone-js/core": "0.8.0"
66
+ },
67
+ "ts-standard": {
68
+ "globals": [
69
+ "it",
70
+ "test",
71
+ "vi",
72
+ "expect",
73
+ "describe",
74
+ "beforeEach"
75
+ ]
76
+ },
77
+ "scripts": {
78
+ "lint": "ts-standard src",
79
+ "lint:fix": "ts-standard --fix src tests",
80
+ "predoc": "rimraf docs",
81
+ "doc": "typedoc",
82
+ "clean": "rimraf dist",
83
+ "build": "rollup -c",
84
+ "test": "vitest run",
85
+ "test:cvg": "npm run test -- --coverage",
86
+ "test:text": "npm run test:cvg -- --coverage.reporter=text",
87
+ "test:html": "npm run test:cvg -- --coverage.reporter=html",
88
+ "test:clover": "npm run test:cvg -- --coverage.reporter=clover"
89
+ }
90
+ }