@atscript/moost-validator 0.1.88 → 0.1.90
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/dist/index.cjs +125 -48
- package/dist/index.mjs +111 -11
- package/package.json +7 -7
package/dist/index.cjs
CHANGED
|
@@ -1,30 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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_utils = __toESM(require("@atscript/typescript/utils"));
|
|
26
|
-
const moost = __toESM(require("moost"));
|
|
27
|
-
const __moostjs_event_http = __toESM(require("@moostjs/event-http"));
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
let _atscript_typescript_utils = require("@atscript/typescript/utils");
|
|
3
|
+
let moost = require("moost");
|
|
4
|
+
let _moostjs_event_http = require("@moostjs/event-http");
|
|
28
5
|
|
|
29
6
|
//#region packages/moost-validator/src/as-coercion.pipe.ts
|
|
30
7
|
/**
|
|
@@ -36,20 +13,63 @@ const __moostjs_event_http = __toESM(require("@moostjs/event-http"));
|
|
|
36
13
|
"QUERY",
|
|
37
14
|
"QUERY_ITEM"
|
|
38
15
|
];
|
|
39
|
-
|
|
16
|
+
/**
|
|
17
|
+
* **coercionPipe** ─ Creates a Moost *pipe* that coerces string-transport
|
|
18
|
+
* input (route params, query strings) toward the parameter's declared type
|
|
19
|
+
* before validation runs.
|
|
20
|
+
*
|
|
21
|
+
* For atscript-annotated types it delegates to `coerceForType` from
|
|
22
|
+
* `@atscript/typescript/utils` (scalars, unions, `@Query()` DTOs, arrays).
|
|
23
|
+
* For plain design types (`offset: number`, `flag: boolean`, `since: Date`)
|
|
24
|
+
* it falls back to direct constructor-based coercion — this also covers
|
|
25
|
+
* scalar `.as` aliases under tsc's `emitDecoratorMetadata`, where the alias
|
|
26
|
+
* identity collapses to `Number`.
|
|
27
|
+
*
|
|
28
|
+
* Coercion never throws and never validates — unparsable input passes
|
|
29
|
+
* through unchanged for {@link validatorPipe} to report. The pipe is
|
|
30
|
+
* registered at {@link TPipePriority.TRANSFORM}, so it composes ahead of
|
|
31
|
+
* `validatorPipe` (VALIDATE):
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* const app = new Moost()
|
|
36
|
+
* app.applyGlobalPipes(coercionPipe(), validatorPipe())
|
|
37
|
+
*
|
|
38
|
+
* // KafkaOffset: @expect.int @expect.min 0 → export type KafkaOffset = number
|
|
39
|
+
* @Get('topics/:topic')
|
|
40
|
+
* read(@Param('offset') offset: KafkaOffset) {
|
|
41
|
+
* // offset is a validated number — no manual Number.parseInt
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @param opts {@link TCoercionOptions}.
|
|
46
|
+
* @returns A ready‑to‑use `PipeFn` instance.
|
|
47
|
+
*/ const coercionPipe = (opts) => {
|
|
40
48
|
const sources = new Set(opts?.sources ?? DEFAULT_SOURCES);
|
|
41
49
|
return (0, moost.definePipeFn)((value, metas) => {
|
|
42
50
|
const source = metas?.targetMeta?.paramSource;
|
|
43
51
|
if (!source || !sources.has(source)) return value;
|
|
44
52
|
const t = metas?.targetMeta?.type;
|
|
45
|
-
if ((0,
|
|
46
|
-
if (t === Number) return (0,
|
|
47
|
-
if (t === Boolean) return (0,
|
|
53
|
+
if ((0, _atscript_typescript_utils.isAnnotatedType)(t)) return (0, _atscript_typescript_utils.coerceForType)(t, value);
|
|
54
|
+
if (t === Number) return (0, _atscript_typescript_utils.coerceScalar)("number", value);
|
|
55
|
+
if (t === Boolean) return (0, _atscript_typescript_utils.coerceScalar)("boolean", value);
|
|
48
56
|
if (t === Date) return coerceDate(value);
|
|
49
57
|
return value;
|
|
50
58
|
}, moost.TPipePriority.TRANSFORM);
|
|
51
59
|
};
|
|
52
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Syntactic sugar decorator that applies {@link coercionPipe} to a handler or
|
|
62
|
+
* an entire controller class.
|
|
63
|
+
*
|
|
64
|
+
* @param opts {@link TCoercionOptions}.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* @Get('items')
|
|
69
|
+
* @UseCoercionPipe()
|
|
70
|
+
* list(@Query() query: SearchQuery) {}
|
|
71
|
+
* ```
|
|
72
|
+
*/ const UseCoercionPipe = (opts) => (0, moost.Pipe)(coercionPipe(opts));
|
|
53
73
|
function coerceDate(value) {
|
|
54
74
|
if (typeof value !== "string" || value.trim().length === 0) return value;
|
|
55
75
|
const parsed = new Date(value);
|
|
@@ -58,19 +78,67 @@ function coerceDate(value) {
|
|
|
58
78
|
|
|
59
79
|
//#endregion
|
|
60
80
|
//#region packages/moost-validator/src/as-validator.pipe.ts
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
81
|
+
/**
|
|
82
|
+
* **validatorPipe** ─ Creates a Moost *pipe* that runs atscript validation on
|
|
83
|
+
* handler parameters (body, params, query, etc.).
|
|
84
|
+
*
|
|
85
|
+
* The pipe inspects the runtime metadata supplied by Moost; when the target
|
|
86
|
+
* parameter type is an atscript‑annotated class or interface it calls
|
|
87
|
+
* `type.validator(opts).validate(value)` to perform synchronous validation.
|
|
88
|
+
*
|
|
89
|
+
* The pipe is registered at {@link TPipePriority.VALIDATE}, ensuring it fires
|
|
90
|
+
* before any transformation pipes and long before business logic executes.
|
|
91
|
+
*
|
|
92
|
+
* @param opts {@link TValidatorOptions}.
|
|
93
|
+
* @returns A ready‑to‑use `PipeFn` instance.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* // for method:
|
|
98
|
+
* @Post()
|
|
99
|
+
* @Pipe(validatorPipe())
|
|
100
|
+
* async create(@Body() dto: CreateUserDto) {}
|
|
101
|
+
*
|
|
102
|
+
* // or globally:
|
|
103
|
+
* const app = new Moost();
|
|
104
|
+
* app.applyGlobalPipes(validatorPipe());
|
|
105
|
+
* ```
|
|
106
|
+
*/ const validatorPipe = (opts) => (0, moost.definePipeFn)((value, metas, level) => {
|
|
107
|
+
if (metas?.targetMeta?.optional && (value === void 0 || value === null)) return value;
|
|
108
|
+
if ((0, _atscript_typescript_utils.isAnnotatedType)(metas?.targetMeta?.type) && typeof metas.targetMeta.type.validator === "function") metas.targetMeta.type.validator(opts).validate(value);
|
|
67
109
|
return value;
|
|
68
110
|
}, moost.TPipePriority.VALIDATE);
|
|
69
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Syntactic sugar decorator that applies {@link validatorPipe} to a handler or
|
|
113
|
+
* an entire controller class.
|
|
114
|
+
*
|
|
115
|
+
* @param opts {@link TValidatorOptions}.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* // for method:
|
|
120
|
+
* @Post()
|
|
121
|
+
* @UseValidatorPipe()
|
|
122
|
+
* async create(@Body() dto: CreateUserDto) {}
|
|
123
|
+
* ```
|
|
124
|
+
*/ const UseValidatorPipe = (opts) => (0, moost.Pipe)(validatorPipe(opts));
|
|
70
125
|
|
|
71
126
|
//#endregion
|
|
72
127
|
//#region packages/moost-validator/src/error-transform.ts
|
|
73
|
-
|
|
128
|
+
/**
|
|
129
|
+
* **validationErrorTransform** ─ Moost interceptor that catches
|
|
130
|
+
* {@link ValidatorError}s thrown by {@link validatorPipe} (or manually) and
|
|
131
|
+
* converts them into a structured `HttpError(400)`
|
|
132
|
+
*
|
|
133
|
+
* Applied at {@link TInterceptorPriority.CATCH_ERROR}
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```ts
|
|
137
|
+
* // apply globally
|
|
138
|
+
* const app = new Moost();
|
|
139
|
+
* app.applyGlobalInterceptors(validationErrorTransform());
|
|
140
|
+
* ```
|
|
141
|
+
*/ const validationErrorTransform = () => (0, moost.defineInterceptor)({
|
|
74
142
|
after: transformValidationError,
|
|
75
143
|
error: transformValidationError
|
|
76
144
|
}, moost.TInterceptorPriority.CATCH_ERROR);
|
|
@@ -78,18 +146,27 @@ const validationErrorTransform = () => (0, moost.defineInterceptor)({
|
|
|
78
146
|
* Internal helper that performs the actual conversion: wraps a
|
|
79
147
|
* `ValidatorError` into {@link HttpError} and passes it to Moost's `reply`.
|
|
80
148
|
*/ function transformValidationError(error, reply) {
|
|
81
|
-
if (error instanceof
|
|
149
|
+
if (error instanceof _atscript_typescript_utils.ValidatorError) reply(new _moostjs_event_http.HttpError(400, {
|
|
82
150
|
message: error.message,
|
|
83
151
|
statusCode: 400,
|
|
84
152
|
_body: error.errors
|
|
85
153
|
}));
|
|
86
154
|
}
|
|
87
|
-
|
|
155
|
+
/**
|
|
156
|
+
* Decorator that registers {@link validationErrorTransform} on a controller or
|
|
157
|
+
* route handler.
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```ts
|
|
161
|
+
* // for method:
|
|
162
|
+
* @Post()
|
|
163
|
+
* @UseValidationErrorTransform()
|
|
164
|
+
*/ const UseValidationErrorTransform = () => (0, moost.Intercept)(validationErrorTransform());
|
|
88
165
|
|
|
89
166
|
//#endregion
|
|
90
|
-
exports.UseCoercionPipe = UseCoercionPipe
|
|
91
|
-
exports.UseValidationErrorTransform = UseValidationErrorTransform
|
|
92
|
-
exports.UseValidatorPipe = UseValidatorPipe
|
|
93
|
-
exports.coercionPipe = coercionPipe
|
|
94
|
-
exports.validationErrorTransform = validationErrorTransform
|
|
95
|
-
exports.validatorPipe = validatorPipe
|
|
167
|
+
exports.UseCoercionPipe = UseCoercionPipe;
|
|
168
|
+
exports.UseValidationErrorTransform = UseValidationErrorTransform;
|
|
169
|
+
exports.UseValidatorPipe = UseValidatorPipe;
|
|
170
|
+
exports.coercionPipe = coercionPipe;
|
|
171
|
+
exports.validationErrorTransform = validationErrorTransform;
|
|
172
|
+
exports.validatorPipe = validatorPipe;
|
package/dist/index.mjs
CHANGED
|
@@ -12,7 +12,38 @@ import { HttpError } from "@moostjs/event-http";
|
|
|
12
12
|
"QUERY",
|
|
13
13
|
"QUERY_ITEM"
|
|
14
14
|
];
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* **coercionPipe** ─ Creates a Moost *pipe* that coerces string-transport
|
|
17
|
+
* input (route params, query strings) toward the parameter's declared type
|
|
18
|
+
* before validation runs.
|
|
19
|
+
*
|
|
20
|
+
* For atscript-annotated types it delegates to `coerceForType` from
|
|
21
|
+
* `@atscript/typescript/utils` (scalars, unions, `@Query()` DTOs, arrays).
|
|
22
|
+
* For plain design types (`offset: number`, `flag: boolean`, `since: Date`)
|
|
23
|
+
* it falls back to direct constructor-based coercion — this also covers
|
|
24
|
+
* scalar `.as` aliases under tsc's `emitDecoratorMetadata`, where the alias
|
|
25
|
+
* identity collapses to `Number`.
|
|
26
|
+
*
|
|
27
|
+
* Coercion never throws and never validates — unparsable input passes
|
|
28
|
+
* through unchanged for {@link validatorPipe} to report. The pipe is
|
|
29
|
+
* registered at {@link TPipePriority.TRANSFORM}, so it composes ahead of
|
|
30
|
+
* `validatorPipe` (VALIDATE):
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* const app = new Moost()
|
|
35
|
+
* app.applyGlobalPipes(coercionPipe(), validatorPipe())
|
|
36
|
+
*
|
|
37
|
+
* // KafkaOffset: @expect.int @expect.min 0 → export type KafkaOffset = number
|
|
38
|
+
* @Get('topics/:topic')
|
|
39
|
+
* read(@Param('offset') offset: KafkaOffset) {
|
|
40
|
+
* // offset is a validated number — no manual Number.parseInt
|
|
41
|
+
* }
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @param opts {@link TCoercionOptions}.
|
|
45
|
+
* @returns A ready‑to‑use `PipeFn` instance.
|
|
46
|
+
*/ const coercionPipe = (opts) => {
|
|
16
47
|
const sources = new Set(opts?.sources ?? DEFAULT_SOURCES);
|
|
17
48
|
return definePipeFn((value, metas) => {
|
|
18
49
|
const source = metas?.targetMeta?.paramSource;
|
|
@@ -25,7 +56,19 @@ const coercionPipe = (opts) => {
|
|
|
25
56
|
return value;
|
|
26
57
|
}, TPipePriority.TRANSFORM);
|
|
27
58
|
};
|
|
28
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Syntactic sugar decorator that applies {@link coercionPipe} to a handler or
|
|
61
|
+
* an entire controller class.
|
|
62
|
+
*
|
|
63
|
+
* @param opts {@link TCoercionOptions}.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* @Get('items')
|
|
68
|
+
* @UseCoercionPipe()
|
|
69
|
+
* list(@Query() query: SearchQuery) {}
|
|
70
|
+
* ```
|
|
71
|
+
*/ const UseCoercionPipe = (opts) => Pipe(coercionPipe(opts));
|
|
29
72
|
function coerceDate(value) {
|
|
30
73
|
if (typeof value !== "string" || value.trim().length === 0) return value;
|
|
31
74
|
const parsed = new Date(value);
|
|
@@ -34,19 +77,67 @@ function coerceDate(value) {
|
|
|
34
77
|
|
|
35
78
|
//#endregion
|
|
36
79
|
//#region packages/moost-validator/src/as-validator.pipe.ts
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
80
|
+
/**
|
|
81
|
+
* **validatorPipe** ─ Creates a Moost *pipe* that runs atscript validation on
|
|
82
|
+
* handler parameters (body, params, query, etc.).
|
|
83
|
+
*
|
|
84
|
+
* The pipe inspects the runtime metadata supplied by Moost; when the target
|
|
85
|
+
* parameter type is an atscript‑annotated class or interface it calls
|
|
86
|
+
* `type.validator(opts).validate(value)` to perform synchronous validation.
|
|
87
|
+
*
|
|
88
|
+
* The pipe is registered at {@link TPipePriority.VALIDATE}, ensuring it fires
|
|
89
|
+
* before any transformation pipes and long before business logic executes.
|
|
90
|
+
*
|
|
91
|
+
* @param opts {@link TValidatorOptions}.
|
|
92
|
+
* @returns A ready‑to‑use `PipeFn` instance.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```ts
|
|
96
|
+
* // for method:
|
|
97
|
+
* @Post()
|
|
98
|
+
* @Pipe(validatorPipe())
|
|
99
|
+
* async create(@Body() dto: CreateUserDto) {}
|
|
100
|
+
*
|
|
101
|
+
* // or globally:
|
|
102
|
+
* const app = new Moost();
|
|
103
|
+
* app.applyGlobalPipes(validatorPipe());
|
|
104
|
+
* ```
|
|
105
|
+
*/ const validatorPipe = (opts) => definePipeFn((value, metas, level) => {
|
|
106
|
+
if (metas?.targetMeta?.optional && (value === void 0 || value === null)) return value;
|
|
107
|
+
if (isAnnotatedType(metas?.targetMeta?.type) && typeof metas.targetMeta.type.validator === "function") metas.targetMeta.type.validator(opts).validate(value);
|
|
43
108
|
return value;
|
|
44
109
|
}, TPipePriority.VALIDATE);
|
|
45
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Syntactic sugar decorator that applies {@link validatorPipe} to a handler or
|
|
112
|
+
* an entire controller class.
|
|
113
|
+
*
|
|
114
|
+
* @param opts {@link TValidatorOptions}.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```ts
|
|
118
|
+
* // for method:
|
|
119
|
+
* @Post()
|
|
120
|
+
* @UseValidatorPipe()
|
|
121
|
+
* async create(@Body() dto: CreateUserDto) {}
|
|
122
|
+
* ```
|
|
123
|
+
*/ const UseValidatorPipe = (opts) => Pipe(validatorPipe(opts));
|
|
46
124
|
|
|
47
125
|
//#endregion
|
|
48
126
|
//#region packages/moost-validator/src/error-transform.ts
|
|
49
|
-
|
|
127
|
+
/**
|
|
128
|
+
* **validationErrorTransform** ─ Moost interceptor that catches
|
|
129
|
+
* {@link ValidatorError}s thrown by {@link validatorPipe} (or manually) and
|
|
130
|
+
* converts them into a structured `HttpError(400)`
|
|
131
|
+
*
|
|
132
|
+
* Applied at {@link TInterceptorPriority.CATCH_ERROR}
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* // apply globally
|
|
137
|
+
* const app = new Moost();
|
|
138
|
+
* app.applyGlobalInterceptors(validationErrorTransform());
|
|
139
|
+
* ```
|
|
140
|
+
*/ const validationErrorTransform = () => defineInterceptor({
|
|
50
141
|
after: transformValidationError,
|
|
51
142
|
error: transformValidationError
|
|
52
143
|
}, TInterceptorPriority.CATCH_ERROR);
|
|
@@ -60,7 +151,16 @@ const validationErrorTransform = () => defineInterceptor({
|
|
|
60
151
|
_body: error.errors
|
|
61
152
|
}));
|
|
62
153
|
}
|
|
63
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Decorator that registers {@link validationErrorTransform} on a controller or
|
|
156
|
+
* route handler.
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```ts
|
|
160
|
+
* // for method:
|
|
161
|
+
* @Post()
|
|
162
|
+
* @UseValidationErrorTransform()
|
|
163
|
+
*/ const UseValidationErrorTransform = () => Intercept(validationErrorTransform());
|
|
64
164
|
|
|
65
165
|
//#endregion
|
|
66
166
|
export { UseCoercionPipe, UseValidationErrorTransform, UseValidatorPipe, coercionPipe, validationErrorTransform, validatorPipe };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/moost-validator",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.90",
|
|
4
4
|
"description": "Validator pipe and utils for Moost.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"annotations",
|
|
@@ -36,15 +36,15 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@moostjs/event-http": "^0.6.
|
|
40
|
-
"moost": "^0.6.
|
|
39
|
+
"@moostjs/event-http": "^0.6.36",
|
|
40
|
+
"moost": "^0.6.36",
|
|
41
41
|
"vitest": "3.2.7"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"@moostjs/event-http": "^0.6.
|
|
45
|
-
"moost": "^0.6.
|
|
46
|
-
"@atscript/core": "^0.1.
|
|
47
|
-
"@atscript/typescript": "^0.1.
|
|
44
|
+
"@moostjs/event-http": "^0.6.36",
|
|
45
|
+
"moost": "^0.6.36",
|
|
46
|
+
"@atscript/core": "^0.1.90",
|
|
47
|
+
"@atscript/typescript": "^0.1.90"
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
50
|
"pub": "pnpm publish --access public",
|