@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 +21 -0
- package/README.md +108 -0
- package/dist/index.cjs +61 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.mjs +34 -0
- package/package.json +51 -0
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
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|