@fluojs/validation 1.0.0-beta.3 → 1.0.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/README.ko.md CHANGED
@@ -26,6 +26,7 @@ pnpm add @fluojs/validation
26
26
  - 컨트롤러나 서비스에서 ad hoc parsing 대신 class 기반 검증 규칙을 쓰고 싶을 때
27
27
  - `PickType`, `PartialType`, `IntersectionType` 같은 metadata-preserving mapped DTO helper가 필요할 때
28
28
  - `@ValidateClass(...)`로 Zod나 Valibot 같은 Standard Schema validator를 붙이고 싶을 때
29
+ - `@fluojs/i18n/validation`으로 명시적으로 localize할 수 있는 안정적인 validation issue code와 path가 필요할 때
29
30
 
30
31
  ## 빠른 시작
31
32
 
@@ -64,10 +65,17 @@ try {
64
65
  - **`materialize<T>(value, target)`**: **입력 처리**에 가장 적합합니다. plain 객체를 받아 대상 클래스의 인스턴스를 생성하고, 값을 복사하며, 중첩된 DTO를 재귀적으로 처리한 후 모든 검증 규칙을 실행합니다.
65
66
  - **`validate(instance, target)`**: **기존 루트 객체 확인**에 적합합니다. 이미 생성된 루트 값에 대해 검증 규칙을 실행하며, plain 객체인 `@ValidateNested(...)` 값은 중첩 DTO 규칙을 실행하기 위해 임시로 실체화할 수 있습니다. 이 임시 실체화는 호출자가 넘긴 속성 값을 대체하지 않습니다.
66
67
 
68
+ `validate()`는 문자열, 배열, `null`, `undefined` 같은 잘못된 루트 값을 field 또는
69
+ class rule이 실행되기 전에 deterministic `DtoValidationError`로 거부합니다. 이미
70
+ 생성된 대상 DTO 인스턴스와 plain 루트 객체는 허용하므로 request-pipeline binder가
71
+ 준비한 DTO payload를 scalar coercion 없이 검증할 수 있습니다.
72
+
67
73
  `materialize()`는 plain 입력 객체의 안전한 own enumerable 속성을 복사하고,
68
74
  DTO 바인딩 메타데이터를 적용한 뒤 `@ValidateNested(...)` 필드를 재귀적으로
69
75
  실체화합니다. 어떤 요청 소스를 선택하고 스칼라 값을 변환할지는 transport 또는
70
76
  binder가 검증 전에 담당한다는 request-pipeline 계약을 유지합니다.
77
+ 선언된 중첩 DTO의 인스턴스인 기존 중첩 값은 그대로 보존하고, plain 중첩 값만
78
+ 해당 필드 또는 collection entry 단위로 실체화합니다.
71
79
  `materialize()`에 넘기는 루트 값은 plain 객체이거나 대상 DTO 인스턴스여야 합니다.
72
80
  문자열, 배열, `null` 같은 잘못된 루트 값은 대상 DTO 생성자나 필드 initializer가
73
81
  실행되기 전에 거부됩니다.
@@ -89,6 +97,8 @@ type ValidationIssue = {
89
97
  사용합니다. HTTP 바인딩에서 온 규칙은 `source`를 붙이며, standalone validation이나
90
98
  Standard Schema 이슈에서는 값이 없을 수 있습니다.
91
99
 
100
+ `@fluojs/validation`은 항상 기존 human-readable `message` 값을 방출합니다. Localized message가 필요한 애플리케이션은 validation 실패 후 `@fluojs/i18n/validation`에 opt-in할 수 있습니다. 이 통합은 validator 실행을 바꾸거나 이 패키지를 HTTP locale resolution에 결합하지 않고 `source`, `field`, `code`를 translation key로 매핑합니다.
101
+
92
102
  ### Mapped DTO 헬퍼
93
103
 
94
104
  ```ts
@@ -124,10 +134,12 @@ class RestrictedUserDto {
124
134
  ### 중첩 검증
125
135
 
126
136
  `@ValidateNested(...)`는 객체 필드, 배열, `Set`, `Map`을 지원합니다. 중첩 DTO path는 validation issue에서 dot/index 표기법을 사용하며, cycle은 안전하게 감지되고 shared reference는 허용됩니다.
137
+ 중첩 타입을 지연 해석해야 할 때는 DTO 클래스 자체나 `() => ChildDto`, `function resolveChildDto() { return ChildDto; }` 같은 lazy constructor factory를 전달할 수 있습니다.
127
138
 
128
139
  ### 암묵적 scalar coercion 없음
129
140
 
130
141
  `materialize()`는 의도적으로 엄격합니다. Transport가 `'42'`를 넘기고 DTO가 `number`를 기대한다면, transport나 binding layer가 먼저 변환해야 합니다.
142
+ `@IsLatitude()`, `@IsLongitude()`를 포함한 numeric validator는 numeric string을 이미 변환된 number처럼 취급하지 않고 DTO의 numeric 값을 검증합니다.
131
143
 
132
144
  ## 공개 API
133
145
 
@@ -145,6 +157,7 @@ class RestrictedUserDto {
145
157
  ## 관련 패키지
146
158
 
147
159
  - `@fluojs/http`: request data를 bind한 뒤 이 패키지로 검증합니다.
160
+ - `@fluojs/i18n`: opt-in localized validation issue message를 위한 `@fluojs/i18n/validation`을 제공합니다.
148
161
  - `@fluojs/serialization`: response side에서 output DTO를 가공합니다.
149
162
  - `@fluojs/core`: validation decorator가 사용하는 metadata primitive를 제공합니다.
150
163
 
package/README.md CHANGED
@@ -26,6 +26,7 @@ pnpm add @fluojs/validation
26
26
  - when you want class-based validation rules instead of ad hoc parsing in controllers and services
27
27
  - when you need metadata-preserving mapped DTO helpers such as `PickType`, `PartialType`, and `IntersectionType`
28
28
  - when you want to attach Standard Schema validators such as Zod or Valibot through `@ValidateClass(...)`
29
+ - when you want stable validation issue codes and paths that can be localized explicitly through `@fluojs/i18n/validation`
29
30
 
30
31
  ## Quick Start
31
32
 
@@ -66,10 +67,19 @@ try {
66
67
  temporarily materialize plain nested `@ValidateNested(...)` values to run their
67
68
  nested DTO rules without replacing the caller's properties
68
69
 
70
+ `validate()` rejects malformed roots such as strings, arrays, `null`, and
71
+ `undefined` with a deterministic `DtoValidationError` before field or class rules
72
+ run. It accepts already-created target DTO instances and plain root objects so
73
+ request-pipeline binders can validate their prepared DTO payloads without scalar
74
+ coercion.
75
+
69
76
  `materialize()` copies safe own enumerable properties from plain input objects,
70
77
  applies DTO binding metadata, and recursively hydrates `@ValidateNested(...)`
71
78
  fields. It preserves the request-pipeline contract that transports or binders own
72
79
  source selection and scalar conversion before validation runs.
80
+ Existing nested values that are already instances of the declared nested DTO are
81
+ preserved; plain nested values are hydrated only for the affected nested field or
82
+ collection entry.
73
83
  The root value passed to `materialize()` must already be a plain object or an
74
84
  instance of the target DTO; malformed roots such as strings, arrays, and `null`
75
85
  are rejected before the target DTO constructor or field initializers run.
@@ -91,6 +101,8 @@ Nested DTOs use dot paths and collection indexes, such as `address.city` or
91
101
  `items[0].name`. HTTP bindings attach `source` when the rule came from request
92
102
  metadata; standalone validation and Standard Schema issues may leave it unset.
93
103
 
104
+ `@fluojs/validation` always emits its normal human-readable `message` values. Applications that need localized messages can opt into `@fluojs/i18n/validation` after validation fails. That integration maps `source`, `field`, and `code` to translation keys without changing validator execution or coupling this package to HTTP locale resolution.
105
+
94
106
  ### Mapped DTO helpers
95
107
 
96
108
  ```ts
@@ -126,10 +138,12 @@ class RestrictedUserDto {
126
138
  ### Nested validation
127
139
 
128
140
  `@ValidateNested(...)` supports object fields, arrays, `Set`, and `Map`. Nested DTO paths use dot/index notation in validation issues, cycles are detected safely, and shared references are allowed.
141
+ Pass either a DTO class or a lazy constructor factory such as `() => ChildDto` or `function resolveChildDto() { return ChildDto; }` when nested types need deferred resolution.
129
142
 
130
143
  ### No implicit scalar coercion
131
144
 
132
145
  `materialize()` is intentionally strict. If a transport gives you `'42'` and your DTO expects `number`, the transport or binding layer must convert it first.
146
+ Numeric validators, including `@IsLatitude()` and `@IsLongitude()`, validate numeric DTO values without treating numeric strings as already-converted numbers.
133
147
 
134
148
  ## Public API
135
149
 
@@ -147,6 +161,7 @@ class RestrictedUserDto {
147
161
  ## Related Packages
148
162
 
149
163
  - `@fluojs/http`: binds request data, then uses this package to validate it
164
+ - `@fluojs/i18n`: exposes `@fluojs/i18n/validation` for opt-in localized validation issue messages
150
165
  - `@fluojs/serialization`: shapes output DTOs on the response side
151
166
  - `@fluojs/core`: provides the metadata primitives used by validation decorators
152
167
 
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,WAAW,EAEZ,MAAM,cAAc,CAAC;AAatB,OAAO,KAAK,EAAmB,SAAS,EAAE,MAAM,YAAY,CAAC;AAgxB7D;;GAEG;AACH,qBAAa,gBAAiB,YAAW,SAAS;IAC1C,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAM5D,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAczE"}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,WAAW,EAEZ,MAAM,cAAc,CAAC;AAatB,OAAO,KAAK,EAAmB,SAAS,EAAE,MAAM,YAAY,CAAC;AAsyB7D;;GAEG;AACH,qBAAa,gBAAiB,YAAW,SAAS;IAC1C,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ5D,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAYzE"}
@@ -1,8 +1,11 @@
1
1
  import validator from 'validator';
2
2
  import { getClassValidationRules, getDtoBindingSchema, getDtoValidationSchema } from '@fluojs/core/internal';
3
3
  import { DtoValidationError } from './errors.js';
4
+ function isClassConstructor(dto) {
5
+ return typeof dto === 'function' && Function.prototype.toString.call(dto).startsWith('class ');
6
+ }
4
7
  function resolveNestedDto(dto) {
5
- if (typeof dto === 'function' && 'prototype' in dto && dto.prototype) {
8
+ if (isClassConstructor(dto)) {
6
9
  return dto;
7
10
  }
8
11
  return dto();
@@ -283,7 +286,7 @@ const RULE_HANDLERS = {
283
286
  validatorjs: {
284
287
  defaultCode: 'INVALID_FIELD',
285
288
  describe: field => `${field} is invalid.`,
286
- validate: (rule, value) => typeof value === 'string' && runValidatorJs(rule, value)
289
+ validate: (rule, value) => runValidatorJs(rule, value)
287
290
  },
288
291
  arrayContains: {
289
292
  defaultCode: 'ARRAY_CONTAINS',
@@ -399,6 +402,15 @@ function describeValidator(rule, field) {
399
402
  };
400
403
  }
401
404
  function runValidatorJs(rule, value) {
405
+ if (rule.validator === 'latitude') {
406
+ return typeof value === 'number' && Number.isFinite(value) && value >= -90 && value <= 90;
407
+ }
408
+ if (rule.validator === 'longitude') {
409
+ return typeof value === 'number' && Number.isFinite(value) && value >= -180 && value <= 180;
410
+ }
411
+ if (typeof value !== 'string') {
412
+ return false;
413
+ }
402
414
  switch (rule.validator) {
403
415
  case 'alpha':
404
416
  return validator.isAlpha(value);
@@ -470,16 +482,6 @@ function runValidatorJs(rule, value) {
470
482
  return validator.isUUID(value, rule.args?.[0]);
471
483
  case 'iso8601':
472
484
  return validator.isISO8601(value);
473
- case 'latitude':
474
- {
475
- const number = Number(value);
476
- return !Number.isNaN(number) && number >= -90 && number <= 90;
477
- }
478
- case 'longitude':
479
- {
480
- const number = Number(value);
481
- return !Number.isNaN(number) && number >= -180 && number <= 180;
482
- }
483
485
  case 'latLong':
484
486
  return validator.isLatLong(value);
485
487
  default:
@@ -500,6 +502,12 @@ function buildInvalidRootIssue() {
500
502
  message: 'DTO root value must be a plain object.'
501
503
  };
502
504
  }
505
+ function assertValidRootValue(value, target) {
506
+ if (value instanceof target || isPlainObject(value)) {
507
+ return;
508
+ }
509
+ throw new DtoValidationError('Validation failed.', [buildInvalidRootIssue()]);
510
+ }
503
511
  function getRuleValues(value) {
504
512
  return getIterableValues(value) ?? [value];
505
513
  }
@@ -646,14 +654,13 @@ async function collectValidationIssuesInternal(target, value, context, traversal
646
654
  */
647
655
  export class DefaultValidator {
648
656
  async validate(value, target) {
657
+ assertValidRootValue(value, target);
649
658
  const issues = await collectValidationIssues(target, value);
650
659
  if (issues.length === 0) return;
651
660
  throw new DtoValidationError('Validation failed.', issues);
652
661
  }
653
662
  async materialize(value, target) {
654
- if (!(value instanceof target) && !isPlainObject(value)) {
655
- throw new DtoValidationError('Validation failed.', [buildInvalidRootIssue()]);
656
- }
663
+ assertValidRootValue(value, target);
657
664
  const instance = createNestedDtoInstance(target, value, {
658
665
  active: new WeakSet()
659
666
  });
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "decorators",
10
10
  "schema"
11
11
  ],
12
- "version": "1.0.0-beta.3",
12
+ "version": "1.0.0",
13
13
  "private": false,
14
14
  "license": "MIT",
15
15
  "repository": {
@@ -42,7 +42,7 @@
42
42
  "dependencies": {
43
43
  "@standard-schema/spec": "^1.1.0",
44
44
  "validator": "^13.15.26",
45
- "@fluojs/core": "^1.0.0-beta.4"
45
+ "@fluojs/core": "^1.0.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/validator": "^13.15.10",