@atscript/moost-validator 0.1.83 → 0.1.85
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 +33 -0
- package/dist/index.d.ts +59 -1
- package/dist/index.mjs +33 -2
- package/package.json +7 -7
package/dist/index.cjs
CHANGED
|
@@ -26,6 +26,37 @@ const __atscript_typescript_utils = __toESM(require("@atscript/typescript/utils"
|
|
|
26
26
|
const moost = __toESM(require("moost"));
|
|
27
27
|
const __moostjs_event_http = __toESM(require("@moostjs/event-http"));
|
|
28
28
|
|
|
29
|
+
//#region packages/moost-validator/src/as-coercion.pipe.ts
|
|
30
|
+
/**
|
|
31
|
+
* Param sources that arrive as strings over the wire and are safe to coerce
|
|
32
|
+
* by default. `BODY` is deliberately excluded — coercing JSON bodies silently
|
|
33
|
+
* loosens the API contract; opt in via {@link TCoercionOptions.sources}.
|
|
34
|
+
*/ const DEFAULT_SOURCES = [
|
|
35
|
+
"ROUTE",
|
|
36
|
+
"QUERY",
|
|
37
|
+
"QUERY_ITEM"
|
|
38
|
+
];
|
|
39
|
+
const coercionPipe = (opts) => {
|
|
40
|
+
const sources = new Set(opts?.sources ?? DEFAULT_SOURCES);
|
|
41
|
+
return (0, moost.definePipeFn)((value, metas) => {
|
|
42
|
+
const source = metas?.targetMeta?.paramSource;
|
|
43
|
+
if (!source || !sources.has(source)) return value;
|
|
44
|
+
const t = metas?.targetMeta?.type;
|
|
45
|
+
if ((0, __atscript_typescript_utils.isAnnotatedType)(t)) return (0, __atscript_typescript_utils.coerceForType)(t, value);
|
|
46
|
+
if (t === Number) return (0, __atscript_typescript_utils.coerceScalar)("number", value);
|
|
47
|
+
if (t === Boolean) return (0, __atscript_typescript_utils.coerceScalar)("boolean", value);
|
|
48
|
+
if (t === Date) return coerceDate(value);
|
|
49
|
+
return value;
|
|
50
|
+
}, moost.TPipePriority.TRANSFORM);
|
|
51
|
+
};
|
|
52
|
+
const UseCoercionPipe = (opts) => (0, moost.Pipe)(coercionPipe(opts));
|
|
53
|
+
function coerceDate(value) {
|
|
54
|
+
if (typeof value !== "string" || value.trim().length === 0) return value;
|
|
55
|
+
const parsed = new Date(value);
|
|
56
|
+
return Number.isNaN(parsed.getTime()) ? value : parsed;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
//#endregion
|
|
29
60
|
//#region packages/moost-validator/src/as-validator.pipe.ts
|
|
30
61
|
const validatorPipe = (opts) => (0, moost.definePipeFn)((value, metas, level) => {
|
|
31
62
|
if (metas?.targetMeta?.optional && (value === undefined || value === null)) return value;
|
|
@@ -56,7 +87,9 @@ const validationErrorTransform = () => (0, moost.defineInterceptor)({
|
|
|
56
87
|
const UseValidationErrorTransform = () => (0, moost.Intercept)(validationErrorTransform());
|
|
57
88
|
|
|
58
89
|
//#endregion
|
|
90
|
+
exports.UseCoercionPipe = UseCoercionPipe
|
|
59
91
|
exports.UseValidationErrorTransform = UseValidationErrorTransform
|
|
60
92
|
exports.UseValidatorPipe = UseValidatorPipe
|
|
93
|
+
exports.coercionPipe = coercionPipe
|
|
61
94
|
exports.validationErrorTransform = validationErrorTransform
|
|
62
95
|
exports.validatorPipe = validatorPipe
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,63 @@
|
|
|
1
1
|
import * as moost from 'moost';
|
|
2
2
|
import { TValidatorOptions } from '@atscript/typescript/utils';
|
|
3
3
|
|
|
4
|
+
/** Options for configuring {@link coercionPipe} behavior. */
|
|
5
|
+
interface TCoercionOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Param sources (`paramSource` metadata stamped by Moost resolvers) the
|
|
8
|
+
* pipe coerces. Defaults to `['ROUTE', 'QUERY', 'QUERY_ITEM']` — the
|
|
9
|
+
* string-transport sources. Add `'BODY'` to opt in to body coercion.
|
|
10
|
+
*/
|
|
11
|
+
sources?: string[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* **coercionPipe** ─ Creates a Moost *pipe* that coerces string-transport
|
|
15
|
+
* input (route params, query strings) toward the parameter's declared type
|
|
16
|
+
* before validation runs.
|
|
17
|
+
*
|
|
18
|
+
* For atscript-annotated types it delegates to `coerceForType` from
|
|
19
|
+
* `@atscript/typescript/utils` (scalars, unions, `@Query()` DTOs, arrays).
|
|
20
|
+
* For plain design types (`offset: number`, `flag: boolean`, `since: Date`)
|
|
21
|
+
* it falls back to direct constructor-based coercion — this also covers
|
|
22
|
+
* scalar `.as` aliases under tsc's `emitDecoratorMetadata`, where the alias
|
|
23
|
+
* identity collapses to `Number`.
|
|
24
|
+
*
|
|
25
|
+
* Coercion never throws and never validates — unparsable input passes
|
|
26
|
+
* through unchanged for {@link validatorPipe} to report. The pipe is
|
|
27
|
+
* registered at {@link TPipePriority.TRANSFORM}, so it composes ahead of
|
|
28
|
+
* `validatorPipe` (VALIDATE):
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const app = new Moost()
|
|
33
|
+
* app.applyGlobalPipes(coercionPipe(), validatorPipe())
|
|
34
|
+
*
|
|
35
|
+
* // KafkaOffset: @expect.int @expect.min 0 → export type KafkaOffset = number
|
|
36
|
+
* @Get('topics/:topic')
|
|
37
|
+
* read(@Param('offset') offset: KafkaOffset) {
|
|
38
|
+
* // offset is a validated number — no manual Number.parseInt
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @param opts {@link TCoercionOptions}.
|
|
43
|
+
* @returns A ready‑to‑use `PipeFn` instance.
|
|
44
|
+
*/
|
|
45
|
+
declare const coercionPipe: (opts?: TCoercionOptions) => moost.TPipeFn<any>;
|
|
46
|
+
/**
|
|
47
|
+
* Syntactic sugar decorator that applies {@link coercionPipe} to a handler or
|
|
48
|
+
* an entire controller class.
|
|
49
|
+
*
|
|
50
|
+
* @param opts {@link TCoercionOptions}.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* @Get('items')
|
|
55
|
+
* @UseCoercionPipe()
|
|
56
|
+
* list(@Query() query: SearchQuery) {}
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
declare const UseCoercionPipe: (opts?: TCoercionOptions) => ClassDecorator & MethodDecorator & ParameterDecorator;
|
|
60
|
+
|
|
4
61
|
/**
|
|
5
62
|
* **validatorPipe** ─ Creates a Moost *pipe* that runs atscript validation on
|
|
6
63
|
* handler parameters (body, params, query, etc.).
|
|
@@ -71,4 +128,5 @@ declare const validationErrorTransform: () => moost.TInterceptorDef;
|
|
|
71
128
|
*/
|
|
72
129
|
declare const UseValidationErrorTransform: () => ClassDecorator & MethodDecorator;
|
|
73
130
|
|
|
74
|
-
export { UseValidationErrorTransform, UseValidatorPipe, validationErrorTransform, validatorPipe };
|
|
131
|
+
export { UseCoercionPipe, UseValidationErrorTransform, UseValidatorPipe, coercionPipe, validationErrorTransform, validatorPipe };
|
|
132
|
+
export type { TCoercionOptions };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,38 @@
|
|
|
1
|
-
import { ValidatorError, isAnnotatedType } from "@atscript/typescript/utils";
|
|
1
|
+
import { ValidatorError, coerceForType, coerceScalar, isAnnotatedType } from "@atscript/typescript/utils";
|
|
2
2
|
import { Intercept, Pipe, TInterceptorPriority, TPipePriority, defineInterceptor, definePipeFn } from "moost";
|
|
3
3
|
import { HttpError } from "@moostjs/event-http";
|
|
4
4
|
|
|
5
|
+
//#region packages/moost-validator/src/as-coercion.pipe.ts
|
|
6
|
+
/**
|
|
7
|
+
* Param sources that arrive as strings over the wire and are safe to coerce
|
|
8
|
+
* by default. `BODY` is deliberately excluded — coercing JSON bodies silently
|
|
9
|
+
* loosens the API contract; opt in via {@link TCoercionOptions.sources}.
|
|
10
|
+
*/ const DEFAULT_SOURCES = [
|
|
11
|
+
"ROUTE",
|
|
12
|
+
"QUERY",
|
|
13
|
+
"QUERY_ITEM"
|
|
14
|
+
];
|
|
15
|
+
const coercionPipe = (opts) => {
|
|
16
|
+
const sources = new Set(opts?.sources ?? DEFAULT_SOURCES);
|
|
17
|
+
return definePipeFn((value, metas) => {
|
|
18
|
+
const source = metas?.targetMeta?.paramSource;
|
|
19
|
+
if (!source || !sources.has(source)) return value;
|
|
20
|
+
const t = metas?.targetMeta?.type;
|
|
21
|
+
if (isAnnotatedType(t)) return coerceForType(t, value);
|
|
22
|
+
if (t === Number) return coerceScalar("number", value);
|
|
23
|
+
if (t === Boolean) return coerceScalar("boolean", value);
|
|
24
|
+
if (t === Date) return coerceDate(value);
|
|
25
|
+
return value;
|
|
26
|
+
}, TPipePriority.TRANSFORM);
|
|
27
|
+
};
|
|
28
|
+
const UseCoercionPipe = (opts) => Pipe(coercionPipe(opts));
|
|
29
|
+
function coerceDate(value) {
|
|
30
|
+
if (typeof value !== "string" || value.trim().length === 0) return value;
|
|
31
|
+
const parsed = new Date(value);
|
|
32
|
+
return Number.isNaN(parsed.getTime()) ? value : parsed;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
5
36
|
//#region packages/moost-validator/src/as-validator.pipe.ts
|
|
6
37
|
const validatorPipe = (opts) => definePipeFn((value, metas, level) => {
|
|
7
38
|
if (metas?.targetMeta?.optional && (value === undefined || value === null)) return value;
|
|
@@ -32,4 +63,4 @@ const validationErrorTransform = () => defineInterceptor({
|
|
|
32
63
|
const UseValidationErrorTransform = () => Intercept(validationErrorTransform());
|
|
33
64
|
|
|
34
65
|
//#endregion
|
|
35
|
-
export { UseValidationErrorTransform, UseValidatorPipe, validationErrorTransform, validatorPipe };
|
|
66
|
+
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.85",
|
|
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.33",
|
|
40
|
+
"moost": "^0.6.33",
|
|
41
41
|
"vitest": "3.2.4"
|
|
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.33",
|
|
45
|
+
"moost": "^0.6.33",
|
|
46
|
+
"@atscript/core": "^0.1.85",
|
|
47
|
+
"@atscript/typescript": "^0.1.85"
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
50
|
"pub": "pnpm publish --access public",
|