@atscript/moost-validator 0.0.18

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 (c) 2025 Atscript
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,108 @@
1
+ # @atscript/moost-validator
2
+
3
+ **Drop‑in atscript validation for Moost.** This package adds a tiny pipe and an interceptor that let you validate any handler argument, DTO, or DI‑injected value that comes from an `@mongo.collection` / `.as` model – no extra boilerplate, no manual `validate()` calls.
4
+
5
+ ---
6
+
7
+ ## Features
8
+
9
+ - 🛂 **Automatic validation** – if the parameter type has a `validator()` factory, we run it.
10
+ - ⚡ **Fast & sync** – validation happens in the `VALIDATE` pipeline stage before business logic.
11
+ - 🛠️ **Composable** – use as a decorator (`@UseValidatorPipe`) or apply globally.
12
+ - 🧩 **Nice errors out of the box** – interceptor converts `ValidatorError` → `HttpError(400)`.
13
+
14
+ ---
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm i @atscript/moost-validator
20
+ # Or
21
+ pnpm add @atscript/moost-validator
22
+ ```
23
+
24
+ ---
25
+
26
+ ## Quick start
27
+
28
+ ### 1. Register the pipe (pick one)
29
+
30
+ #### a) Globally – affects every parameter/property
31
+
32
+ ```ts
33
+ import { Moost } from 'moost'
34
+ import { validatorPipe } from '@atscript/moost-validator'
35
+
36
+ const app = new Moost()
37
+ app.applyGlobalPipes(validatorPipe())
38
+ ```
39
+
40
+ #### b) Per controller / handler
41
+
42
+ ```ts
43
+ import { Controller, Pipe } from 'moost'
44
+ import { Post, Body } from '@moostjs/event-http'
45
+ import { UseValidatorPipe } from '@atscript/moost-validator'
46
+ import { CreateUserDto } from './user.dto.as'
47
+
48
+ @UseValidatorPipe() // controller‑wide
49
+ @Controller('users')
50
+ export class UsersController {
51
+ @Post()
52
+ @UseValidatorPipe() // or per‑method
53
+ async create(@Body() dto: CreateUserDto) {}
54
+ }
55
+ ```
56
+
57
+ ### 2. Catch validation errors (optional)
58
+
59
+ Global:
60
+
61
+ ```ts
62
+ import { validationErrorTransform } from '@atscript/moost-validator'
63
+
64
+ app.applyGlobalInterceptors(validationErrorTransform())
65
+ ```
66
+
67
+ Per handler:
68
+
69
+ ```ts
70
+ import { UseValidationErrorTransform } from '@atscript/moost-validator'
71
+
72
+ @Post()
73
+ @UseValidationErrorTransform()
74
+ async create(@Body() dto: CreateUserDto) {}
75
+ ```
76
+
77
+ ---
78
+
79
+ ## API reference
80
+
81
+ | Export | Type | Description |
82
+ | ------------------------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
83
+ | `validatorPipe(opts?)` | `PipeFn` | Low‑level factory. Returns a pipe that runs `type.validator(opts).validate(value)` on the argument **if** the type was produced by atscript. Registered with priority `VALIDATE`. |
84
+ | `UseValidatorPipe(opts?)` | `Decorator` | Sugar over `validatorPipe`. Apply to a class, method, parameter, or property. |
85
+ | `validationErrorTransform()` | `InterceptorFn` | Catches `ValidatorError`, wraps it into `HttpError(400)` with `{ message, statusCode, _body }`. Priority `CATCH_ERROR`. |
86
+ | `UseValidationErrorTransform()` | `Decorator` | Sugar over `validationErrorTransform()`. |
87
+
88
+ ### `opts` (`Partial<TValidatorOptions>`)
89
+
90
+ Any options accepted by `atscript.validator(opts)`. E.g. `{ abortEarly: false }`.
91
+
92
+ ---
93
+
94
+ ## How it works (under the hood)
95
+
96
+ 1. **Pipe** checks metadata that Moost attaches to every parameter/property.
97
+ 2. If the declared type has a `validator()` factory (i.e. it was generated from
98
+ `.as` file with atscript), the pipe instantiates the validator **once** and
99
+ runs `validate(value)`.
100
+ 3. On failure the validator throws `ValidatorError`.
101
+ 4. **Interceptor** catches that error and converts it to a standard Moost
102
+ `HttpError` so your REST adapter sends a clean `400 Bad Request` body.
103
+
104
+ ---
105
+
106
+ ## License
107
+
108
+ MIT © 2025 Artem Maltsev
package/dist/index.cjs ADDED
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ //#region rolldown:runtime
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
23
+
24
+ //#endregion
25
+ const __atscript_typescript = __toESM(require("@atscript/typescript"));
26
+ const moost = __toESM(require("moost"));
27
+ const __moostjs_event_http = __toESM(require("@moostjs/event-http"));
28
+
29
+ //#region packages/moost-validator/src/as-validator.pipe.ts
30
+ const validatorPipe = (opts) => (0, moost.definePipeFn)((value, metas, level) => {
31
+ if ((0, __atscript_typescript.isAnnotatedType)(metas?.targetMeta?.type) && typeof metas.targetMeta.type.validator === "function") {
32
+ const validator = metas.targetMeta.type.validator(opts);
33
+ validator.validate(value);
34
+ }
35
+ return value;
36
+ }, moost.TPipePriority.VALIDATE);
37
+ const UseValidatorPipe = (opts) => (0, moost.Pipe)(validatorPipe(opts));
38
+
39
+ //#endregion
40
+ //#region packages/moost-validator/src/error-transform.ts
41
+ const validationErrorTransform = () => (0, moost.defineInterceptorFn)((before, after, onError) => {
42
+ after(transformValidationError);
43
+ onError(transformValidationError);
44
+ }, moost.TInterceptorPriority.CATCH_ERROR);
45
+ /**
46
+ * Internal helper that performs the actual conversion: wraps a
47
+ * `ValidatorError` into {@link HttpError} and passes it to Moost's `reply`.
48
+ */ function transformValidationError(error, reply) {
49
+ if (error instanceof __atscript_typescript.ValidatorError) reply(new __moostjs_event_http.HttpError(400, {
50
+ message: error.message,
51
+ statusCode: 400,
52
+ _body: error.errors
53
+ }));
54
+ }
55
+ const UseValidationErrorTransform = () => (0, moost.Intercept)(validationErrorTransform());
56
+
57
+ //#endregion
58
+ exports.UseValidationErrorTransform = UseValidationErrorTransform
59
+ exports.UseValidatorPipe = UseValidatorPipe
60
+ exports.validationErrorTransform = validationErrorTransform
61
+ exports.validatorPipe = validatorPipe
@@ -0,0 +1,74 @@
1
+ import * as moost from 'moost';
2
+ import { TValidatorOptions } from '@atscript/typescript';
3
+
4
+ /**
5
+ * **validatorPipe** ─ Creates a Moost *pipe* that runs atscript validation on
6
+ * handler parameters (body, params, query, etc.).
7
+ *
8
+ * The pipe inspects the runtime metadata supplied by Moost; when the target
9
+ * parameter type is an atscript‑annotated class or interface it calls
10
+ * `type.validator(opts).validate(value)` to perform synchronous validation.
11
+ *
12
+ * The pipe is registered at {@link TPipePriority.VALIDATE}, ensuring it fires
13
+ * before any transformation pipes and long before business logic executes.
14
+ *
15
+ * @param opts {@link TValidatorOptions}.
16
+ * @returns A ready‑to‑use `PipeFn` instance.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * // for method:
21
+ * ‎@Post()
22
+ * ‎@Pipe(validatorPipe())
23
+ * async create(@Body() dto: CreateUserDto) {}
24
+ *
25
+ * // or globally:
26
+ * const app = new Moost();
27
+ * app.applyGlobalPipes(validatorPipe());
28
+ * ```
29
+ */
30
+ declare const validatorPipe: (opts?: Partial<TValidatorOptions>) => moost.TPipeFn<any>;
31
+ /**
32
+ * Syntactic sugar decorator that applies {@link validatorPipe} to a handler or
33
+ * an entire controller class.
34
+ *
35
+ * @param opts {@link TValidatorOptions}.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * // for method:
40
+ * ‎@Post()
41
+ * ‎@UseValidatorPipe()
42
+ * async create(@Body() dto: CreateUserDto) {}
43
+ * ```
44
+ */
45
+ declare const UseValidatorPipe: (opts?: Partial<TValidatorOptions>) => ClassDecorator & MethodDecorator & ParameterDecorator;
46
+
47
+ /**
48
+ * **validationErrorTransform** ─ Moost interceptor that catches
49
+ * {@link ValidatorError}s thrown by {@link validatorPipe} (or manually) and
50
+ * converts them into a structured `HttpError(400)`
51
+ *
52
+ * Applied at {@link TInterceptorPriority.CATCH_ERROR}
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * // apply globally
57
+ * const app = new Moost();
58
+ * app.applyGlobalInterceptors(validationErrorTransform());
59
+ * ```
60
+ */
61
+ declare const validationErrorTransform: () => moost.TInterceptorFn;
62
+ /**
63
+ * Decorator that registers {@link validationErrorTransform} on a controller or
64
+ * route handler.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * // for method:
69
+ * ‎@Post()
70
+ * ‎@UseValidationErrorTransform()
71
+ */
72
+ declare const UseValidationErrorTransform: () => ClassDecorator & MethodDecorator;
73
+
74
+ export { UseValidationErrorTransform, UseValidatorPipe, validationErrorTransform, validatorPipe };
package/dist/index.mjs ADDED
@@ -0,0 +1,34 @@
1
+ import { ValidatorError, isAnnotatedType } from "@atscript/typescript";
2
+ import { Intercept, Pipe, TInterceptorPriority, TPipePriority, defineInterceptorFn, definePipeFn } from "moost";
3
+ import { HttpError } from "@moostjs/event-http";
4
+
5
+ //#region packages/moost-validator/src/as-validator.pipe.ts
6
+ const validatorPipe = (opts) => definePipeFn((value, metas, level) => {
7
+ if (isAnnotatedType(metas?.targetMeta?.type) && typeof metas.targetMeta.type.validator === "function") {
8
+ const validator = metas.targetMeta.type.validator(opts);
9
+ validator.validate(value);
10
+ }
11
+ return value;
12
+ }, TPipePriority.VALIDATE);
13
+ const UseValidatorPipe = (opts) => Pipe(validatorPipe(opts));
14
+
15
+ //#endregion
16
+ //#region packages/moost-validator/src/error-transform.ts
17
+ const validationErrorTransform = () => defineInterceptorFn((before, after, onError) => {
18
+ after(transformValidationError);
19
+ onError(transformValidationError);
20
+ }, TInterceptorPriority.CATCH_ERROR);
21
+ /**
22
+ * Internal helper that performs the actual conversion: wraps a
23
+ * `ValidatorError` into {@link HttpError} and passes it to Moost's `reply`.
24
+ */ function transformValidationError(error, reply) {
25
+ if (error instanceof ValidatorError) reply(new HttpError(400, {
26
+ message: error.message,
27
+ statusCode: 400,
28
+ _body: error.errors
29
+ }));
30
+ }
31
+ const UseValidationErrorTransform = () => Intercept(validationErrorTransform());
32
+
33
+ //#endregion
34
+ export { UseValidationErrorTransform, UseValidatorPipe, validationErrorTransform, validatorPipe };
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@atscript/moost-validator",
3
+ "version": "0.0.18",
4
+ "description": "Validator pipe and utils for Moost.",
5
+ "type": "module",
6
+ "main": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.cjs"
13
+ },
14
+ "./package.json": "./package.json"
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "keywords": [
20
+ "atscript",
21
+ "annotations",
22
+ "typescript",
23
+ "moost",
24
+ "validator"
25
+ ],
26
+ "author": "Artem Maltsev",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/moostjs/atscript.git",
30
+ "directory": "packages/moost-validator"
31
+ },
32
+ "bugs": {
33
+ "url": "https://github.com/moostjs/atscript/issues"
34
+ },
35
+ "homepage": "https://github.com/moostjs/atscript/tree/main/packages/moost-validator#readme",
36
+ "license": "ISC",
37
+ "dependencies": {},
38
+ "devDependencies": {
39
+ "vitest": "3.2.4"
40
+ },
41
+ "peerDependencies": {
42
+ "@moostjs/event-http": "^0.5.30",
43
+ "moost": "^0.5.30",
44
+ "@atscript/core": "^0.0.18",
45
+ "@atscript/typescript": "^0.0.18"
46
+ },
47
+ "scripts": {
48
+ "pub": "pnpm publish --access public",
49
+ "test": "vitest"
50
+ }
51
+ }