skyguard-js 1.1.1 → 1.1.2

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.
Files changed (34) hide show
  1. package/README.md +11 -9
  2. package/dist/crypto/jwt.js +2 -2
  3. package/dist/http/logger.js +1 -1
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.js +3 -2
  6. package/dist/sessions/fileSessionStorage.js +4 -5
  7. package/dist/sessions/sessionStorage.d.ts +1 -1
  8. package/dist/static/contentDisposition.js +6 -1
  9. package/dist/storage/storage.js +1 -4
  10. package/dist/validators/rules/arrayRule.d.ts +3 -8
  11. package/dist/validators/rules/arrayRule.js +18 -44
  12. package/dist/validators/rules/bigIntRule.d.ts +20 -0
  13. package/dist/validators/rules/bigIntRule.js +53 -0
  14. package/dist/validators/rules/booleanRule.d.ts +1 -1
  15. package/dist/validators/rules/dateRule.d.ts +1 -1
  16. package/dist/validators/rules/index.d.ts +3 -1
  17. package/dist/validators/rules/index.js +7 -3
  18. package/dist/validators/rules/numberRule.d.ts +1 -1
  19. package/dist/validators/rules/numberRule.js +6 -7
  20. package/dist/validators/rules/objectRule.d.ts +7 -0
  21. package/dist/validators/rules/objectRule.js +27 -0
  22. package/dist/validators/rules/stringRule.d.ts +11 -2
  23. package/dist/validators/rules/stringRule.js +31 -7
  24. package/dist/validators/rules/{requiredRule.d.ts → unionRule.d.ts} +3 -7
  25. package/dist/validators/rules/unionRule.js +31 -0
  26. package/dist/validators/types.d.ts +2 -5
  27. package/dist/validators/validationRule.d.ts +5 -14
  28. package/dist/validators/validationRule.js +8 -14
  29. package/dist/validators/validationSchema.d.ts +58 -20
  30. package/dist/validators/validationSchema.js +82 -46
  31. package/dist/validators/validator.d.ts +3 -3
  32. package/dist/validators/validator.js +18 -9
  33. package/package.json +7 -7
  34. package/dist/validators/rules/requiredRule.js +0 -21
package/README.md CHANGED
@@ -165,17 +165,17 @@ app.staticFiles(join(__dirname, "..", "static"));
165
165
 
166
166
  ## ⛔ Data Validation
167
167
 
168
- Skyguard.js provides a **declarative validation system** using schemas.
168
+ To validate data in the body of client requests, the framework provides the creation of validation schemas, which are created as follows:
169
169
 
170
170
  ```ts
171
- import { validator } from "skyguard-js/validation";
172
-
173
- const userSchema = validator.schema({
174
- name: validator.string({ maxLength: 60 }),
175
- email: validator.email().required(),
176
- age: validator.number({ min: 18 }),
177
- active: validator.boolean().required(),
178
- birthdate: validator.date({ max: new Date() }),
171
+ import { v, schema } from "skyguard-js";
172
+
173
+ const userSchema = schema({
174
+ name: v.string({ maxLength: 60 }),
175
+ email: v.email(),
176
+ age: v.number({ min: 18 }),
177
+ active: v.boolean().default(false),
178
+ birthdate: v.date({ max: new Date() }),
179
179
  });
180
180
 
181
181
  app.post("/users", (request: Request) => {
@@ -188,6 +188,8 @@ app.post("/users", (request: Request) => {
188
188
  });
189
189
  ```
190
190
 
191
+ By default each property you define in the schema is required, to define it optional you use the `.optional()` or `.default(value)` function
192
+
191
193
  Validation is:
192
194
 
193
195
  - Fail-fast per field
@@ -85,7 +85,7 @@ const verifyJWT = (token, secret) => {
85
85
  return null;
86
86
  return payload;
87
87
  }
88
- catch (error) {
88
+ catch {
89
89
  return null;
90
90
  }
91
91
  };
@@ -105,7 +105,7 @@ const decodeJWT = (token) => {
105
105
  const payload = JSON.parse(base64UrlDecode(parts[1]));
106
106
  return { header, payload };
107
107
  }
108
- catch (error) {
108
+ catch {
109
109
  return null;
110
110
  }
111
111
  };
@@ -13,7 +13,7 @@ class Logger {
13
13
  const diff = process.hrtime.bigint() - startTime;
14
14
  const responseTime = (Number(diff) / 1_000_000).toFixed(3);
15
15
  const coloredStatus = this.colorizeStatus(res.statusCode);
16
- const logLine = `${method} ${url} ${coloredStatus} ${responseTime} ms - ${contentLength}`;
16
+ const logLine = `${method} ${url} ${coloredStatus} ${responseTime} ms - ${contentLength.toString()}`;
17
17
  this.stream.write(logLine + "\n");
18
18
  }
19
19
  colorizeStatus(statusCode) {
package/dist/index.d.ts CHANGED
@@ -6,4 +6,4 @@ export { FileSessionStorage, MemorySessionStorage } from "./sessions";
6
6
  export { HttpMethods } from "./http/httpMethods";
7
7
  export { createUploader } from "./storage/uploader";
8
8
  export { StorageType } from "./storage/types";
9
- export { validator } from "./validators/validationSchema";
9
+ export { v, schema } from "./validators/validationSchema";
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.validator = exports.StorageType = exports.createUploader = exports.HttpMethods = exports.MemorySessionStorage = exports.FileSessionStorage = exports.RouterGroup = exports.Response = exports.Request = exports.createApp = void 0;
3
+ exports.schema = exports.v = exports.StorageType = exports.createUploader = exports.HttpMethods = exports.MemorySessionStorage = exports.FileSessionStorage = exports.RouterGroup = exports.Response = exports.Request = exports.createApp = void 0;
4
4
  var app_1 = require("./app");
5
5
  Object.defineProperty(exports, "createApp", { enumerable: true, get: function () { return app_1.createApp; } });
6
6
  var http_1 = require("./http");
@@ -18,4 +18,5 @@ Object.defineProperty(exports, "createUploader", { enumerable: true, get: functi
18
18
  var types_1 = require("./storage/types");
19
19
  Object.defineProperty(exports, "StorageType", { enumerable: true, get: function () { return types_1.StorageType; } });
20
20
  var validationSchema_1 = require("./validators/validationSchema");
21
- Object.defineProperty(exports, "validator", { enumerable: true, get: function () { return validationSchema_1.validator; } });
21
+ Object.defineProperty(exports, "v", { enumerable: true, get: function () { return validationSchema_1.v; } });
22
+ Object.defineProperty(exports, "schema", { enumerable: true, get: function () { return validationSchema_1.schema; } });
@@ -213,10 +213,7 @@ class FileSessionStorage {
213
213
  }
214
214
  catch {
215
215
  // Corrupt/unreadable → best-effort cleanup
216
- try {
217
- await (0, promises_1.unlink)(full);
218
- }
219
- catch { }
216
+ await (0, promises_1.unlink)(full);
220
217
  }
221
218
  }));
222
219
  }
@@ -280,7 +277,9 @@ class FileSessionStorage {
280
277
  try {
281
278
  await (0, promises_1.unlink)(path);
282
279
  }
283
- catch { }
280
+ catch {
281
+ /** eslint-disable-next-line no-empty */
282
+ }
284
283
  }
285
284
  /**
286
285
  * Generates a cryptographically strong session id (64 hex chars).
@@ -67,7 +67,7 @@ export interface SessionStorage {
67
67
  * Removes all associated data and invalidates the session
68
68
  * for subsequent requests.
69
69
  */
70
- destroy(): void;
70
+ destroy(): void | Promise<void>;
71
71
  }
72
72
  /**
73
73
  * Internal representation of persisted session data.
@@ -59,7 +59,12 @@ class ContentDisposition {
59
59
  }
60
60
  sanitizeFilename(filename) {
61
61
  return filename
62
- .replace(/[\x00-\x1F\x7F-\x9F]/g, "")
62
+ .split("")
63
+ .filter(char => {
64
+ const code = char.charCodeAt(0);
65
+ return !(code <= 0x1f || (code >= 0x7f && code <= 0x9f));
66
+ })
67
+ .join("")
63
68
  .replace(/["\r\n]/g, "")
64
69
  .replace(/[/\\]/g, "")
65
70
  .replace(/\s+/g, " ")
@@ -91,10 +91,7 @@ class DiskStorage {
91
91
  */
92
92
  async removeFile(file) {
93
93
  if (file.path) {
94
- try {
95
- await (0, promises_1.unlink)(file.path);
96
- }
97
- catch { }
94
+ await (0, promises_1.unlink)(file.path);
98
95
  }
99
96
  }
100
97
  /**
@@ -4,13 +4,8 @@ export interface ArrayRuleOptions extends RuleOptions {
4
4
  minLength?: number;
5
5
  maxLength?: number;
6
6
  }
7
- export declare class ArrayRule extends BaseValidationRule {
8
- constructor();
9
- validate(context: ValidationContext, options?: ArrayRuleOptions): ValidationError | null;
10
- string(message?: string): this;
11
- number(message?: string): this;
12
- }
13
- export declare class ArrayNumberRule extends BaseValidationRule {
14
- constructor();
7
+ export declare class ArrayRule extends BaseValidationRule<Array<unknown>> {
8
+ private readonly typeValid?;
9
+ constructor(typeValid?: BaseValidationRule);
15
10
  validate(context: ValidationContext, options?: ArrayRuleOptions): ValidationError | null;
16
11
  }
@@ -1,10 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ArrayNumberRule = exports.ArrayRule = void 0;
3
+ exports.ArrayRule = void 0;
4
4
  const validationRule_1 = require("../validationRule");
5
5
  class ArrayRule extends validationRule_1.BaseValidationRule {
6
- constructor() {
6
+ typeValid;
7
+ constructor(typeValid) {
7
8
  super("array");
9
+ this.typeValid = typeValid;
8
10
  }
9
11
  validate(context, options) {
10
12
  const { field, value } = context;
@@ -17,50 +19,22 @@ class ArrayRule extends validationRule_1.BaseValidationRule {
17
19
  if (options?.maxLength && value.length > options.maxLength) {
18
20
  return this.createError(field, `${field} must have at most ${options.maxLength} items`, value);
19
21
  }
20
- return null;
21
- }
22
- string(message) {
23
- this.rules.push({
24
- rule: new ArrayStringRule(),
25
- options: { message },
26
- });
27
- return this;
28
- }
29
- number(message) {
30
- this.rules.push({
31
- rule: new ArrayNumberRule(),
32
- options: { message },
33
- });
34
- return this;
35
- }
36
- }
37
- exports.ArrayRule = ArrayRule;
38
- class ArrayStringRule extends validationRule_1.BaseValidationRule {
39
- constructor() {
40
- super("arrayString");
41
- }
42
- validate(context, options) {
43
- const { field, value } = context;
44
- for (let i = 0; i < value.length; i++) {
45
- if (typeof value[i] !== "string") {
46
- return this.createError(field, options?.message || `${field} must be an array of strings`, value);
22
+ if (!this.typeValid)
23
+ return null;
24
+ const itemRules = this.typeValid.rules.length
25
+ ? this.typeValid.rules
26
+ : [{ rule: this.typeValid, options: undefined }];
27
+ for (const [index, item] of value.entries()) {
28
+ for (const { rule, options: itemOptions } of itemRules) {
29
+ const error = rule.validate({
30
+ field: `${field}[${index}]`,
31
+ value: item,
32
+ }, itemOptions);
33
+ if (error)
34
+ return error;
47
35
  }
48
36
  }
49
37
  return null;
50
38
  }
51
39
  }
52
- class ArrayNumberRule extends validationRule_1.BaseValidationRule {
53
- constructor() {
54
- super("arrayNumber");
55
- }
56
- validate(context, options) {
57
- const { field, value } = context;
58
- for (let i = 0; i < value.length; i++) {
59
- if (typeof value[i] !== "number") {
60
- return this.createError(field, options?.message || `${field} must be an array of numbers`, value);
61
- }
62
- }
63
- return null;
64
- }
65
- }
66
- exports.ArrayNumberRule = ArrayNumberRule;
40
+ exports.ArrayRule = ArrayRule;
@@ -0,0 +1,20 @@
1
+ import type { ValidationContext, RuleOptions, ValidationError } from "../types";
2
+ import { BaseValidationRule } from "../validationRule";
3
+ export interface BigIntRuleOptions extends RuleOptions {
4
+ gt?: bigint;
5
+ gte?: bigint;
6
+ lt?: bigint;
7
+ lte?: bigint;
8
+ positive?: boolean;
9
+ negative?: boolean;
10
+ }
11
+ export declare class BigIntRule extends BaseValidationRule<bigint> {
12
+ constructor();
13
+ validate(context: ValidationContext, options?: BigIntRuleOptions): ValidationError | null;
14
+ gt(limit: bigint, message?: string): this;
15
+ gte(limit: bigint, message?: string): this;
16
+ lt(limit: bigint, message?: string): this;
17
+ lte(limit: bigint, message?: string): this;
18
+ positive(message?: string): this;
19
+ negative(message?: string): this;
20
+ }
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BigIntRule = void 0;
4
+ const validationRule_1 = require("../validationRule");
5
+ class BigIntRule extends validationRule_1.BaseValidationRule {
6
+ constructor() {
7
+ super("bigint");
8
+ }
9
+ validate(context, options) {
10
+ const { field, value } = context;
11
+ if (typeof value !== "bigint") {
12
+ return this.createError(field, options?.message || `${field} must be an bigint`, value);
13
+ }
14
+ if (options?.positive && value <= 0n)
15
+ return this.createError(field, `${field} must be positive`, value);
16
+ if (options?.negative && value >= 0n)
17
+ return this.createError(field, `${field} must be negative`, value);
18
+ if (options?.gt && value <= options.gt)
19
+ return this.createError(field, `${field} must be greater than ${options.gt}`, value);
20
+ if (options?.gte && value < options.gte)
21
+ return this.createError(field, `${field} must be greater than or equal to ${options.gte}`, value);
22
+ if (options?.lt && value >= options.lt)
23
+ return this.createError(field, `${field} must be less than ${options.lt}`, value);
24
+ if (options?.lte && value > options.lte)
25
+ return this.createError(field, `${field} must be less than or equal to ${options.lte}`, value);
26
+ return null;
27
+ }
28
+ gt(limit, message) {
29
+ this.rules.push({ rule: this, options: { gt: limit, message } });
30
+ return this;
31
+ }
32
+ gte(limit, message) {
33
+ this.rules.push({ rule: this, options: { gte: limit, message } });
34
+ return this;
35
+ }
36
+ lt(limit, message) {
37
+ this.rules.push({ rule: this, options: { lt: limit, message } });
38
+ return this;
39
+ }
40
+ lte(limit, message) {
41
+ this.rules.push({ rule: this, options: { lte: limit, message } });
42
+ return this;
43
+ }
44
+ positive(message) {
45
+ this.rules.push({ rule: this, options: { positive: true, message } });
46
+ return this;
47
+ }
48
+ negative(message) {
49
+ this.rules.push({ rule: this, options: { negative: true, message } });
50
+ return this;
51
+ }
52
+ }
53
+ exports.BigIntRule = BigIntRule;
@@ -5,7 +5,7 @@ import { BaseValidationRule } from "../validationRule";
5
5
  *
6
6
  * Validates that a value is a boolean (`true` or `false`).
7
7
  */
8
- export declare class BooleanRule extends BaseValidationRule {
8
+ export declare class BooleanRule extends BaseValidationRule<boolean> {
9
9
  constructor();
10
10
  validate(context: ValidationContext, options?: RuleOptions): ValidationError | null;
11
11
  }
@@ -10,7 +10,7 @@ export interface DateRuleOptions extends RuleOptions {
10
10
  *
11
11
  * Validates that a value represents a valid date.
12
12
  */
13
- export declare class DateRule extends BaseValidationRule {
13
+ export declare class DateRule extends BaseValidationRule<Date> {
14
14
  constructor();
15
15
  validate(context: ValidationContext, options?: DateRuleOptions): ValidationError | null;
16
16
  }
@@ -1,7 +1,9 @@
1
1
  export { BooleanRule } from "./booleanRule";
2
2
  export { DateRule, DateRuleOptions } from "./dateRule";
3
3
  export { NumberRule, NumberRuleOptions } from "./numberRule";
4
- export { RequiredRule } from "./requiredRule";
5
4
  export { StringRule, StringRuleOptions } from "./stringRule";
6
5
  export { ArrayRule, ArrayRuleOptions } from "./arrayRule";
7
6
  export { LiteralRule } from "./literalRule";
7
+ export { ObjectRule } from "./objectRule";
8
+ export { UnionRule } from "./unionRule";
9
+ export { BigIntRule, BigIntRuleOptions } from "./bigIntRule";
@@ -1,17 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LiteralRule = exports.ArrayRule = exports.StringRule = exports.RequiredRule = exports.NumberRule = exports.DateRule = exports.BooleanRule = void 0;
3
+ exports.BigIntRule = exports.UnionRule = exports.ObjectRule = exports.LiteralRule = exports.ArrayRule = exports.StringRule = exports.NumberRule = exports.DateRule = exports.BooleanRule = void 0;
4
4
  var booleanRule_1 = require("./booleanRule");
5
5
  Object.defineProperty(exports, "BooleanRule", { enumerable: true, get: function () { return booleanRule_1.BooleanRule; } });
6
6
  var dateRule_1 = require("./dateRule");
7
7
  Object.defineProperty(exports, "DateRule", { enumerable: true, get: function () { return dateRule_1.DateRule; } });
8
8
  var numberRule_1 = require("./numberRule");
9
9
  Object.defineProperty(exports, "NumberRule", { enumerable: true, get: function () { return numberRule_1.NumberRule; } });
10
- var requiredRule_1 = require("./requiredRule");
11
- Object.defineProperty(exports, "RequiredRule", { enumerable: true, get: function () { return requiredRule_1.RequiredRule; } });
12
10
  var stringRule_1 = require("./stringRule");
13
11
  Object.defineProperty(exports, "StringRule", { enumerable: true, get: function () { return stringRule_1.StringRule; } });
14
12
  var arrayRule_1 = require("./arrayRule");
15
13
  Object.defineProperty(exports, "ArrayRule", { enumerable: true, get: function () { return arrayRule_1.ArrayRule; } });
16
14
  var literalRule_1 = require("./literalRule");
17
15
  Object.defineProperty(exports, "LiteralRule", { enumerable: true, get: function () { return literalRule_1.LiteralRule; } });
16
+ var objectRule_1 = require("./objectRule");
17
+ Object.defineProperty(exports, "ObjectRule", { enumerable: true, get: function () { return objectRule_1.ObjectRule; } });
18
+ var unionRule_1 = require("./unionRule");
19
+ Object.defineProperty(exports, "UnionRule", { enumerable: true, get: function () { return unionRule_1.UnionRule; } });
20
+ var bigIntRule_1 = require("./bigIntRule");
21
+ Object.defineProperty(exports, "BigIntRule", { enumerable: true, get: function () { return bigIntRule_1.BigIntRule; } });
@@ -12,7 +12,7 @@ export interface NumberRuleOptions extends RuleOptions {
12
12
  *
13
13
  * Validates that a value is a number.
14
14
  */
15
- export declare class NumberRule extends BaseValidationRule {
15
+ export declare class NumberRule extends BaseValidationRule<number> {
16
16
  constructor();
17
17
  validate(context: ValidationContext, options?: NumberRuleOptions): ValidationError | null;
18
18
  }
@@ -13,18 +13,17 @@ class NumberRule extends validationRule_1.BaseValidationRule {
13
13
  }
14
14
  validate(context, options) {
15
15
  const { field, value } = context;
16
- const num = typeof value === "string" ? Number(value) : value;
17
- if (typeof num !== "number" || isNaN(num))
16
+ if (typeof value !== "number")
18
17
  return this.createError(field, options?.message || `${field} must be a number`, value);
19
- if (options?.integer && !Number.isInteger(num))
18
+ if (options?.integer && !Number.isInteger(value))
20
19
  return this.createError(field, `${field} must be an integer`, value);
21
- if (options?.positive && num <= 0)
20
+ if (options?.positive && value <= 0)
22
21
  return this.createError(field, `${field} must be positive`, value);
23
- if (options?.negative && num >= 0)
22
+ if (options?.negative && value >= 0)
24
23
  return this.createError(field, `${field} must be negative`, value);
25
- if (options?.min && num < options.min)
24
+ if (options?.min && value < options.min)
26
25
  return this.createError(field, `${field} must be at least ${options.min}`, value);
27
- if (options?.max && num > options.max)
26
+ if (options?.max && value > options.max)
28
27
  return this.createError(field, `${field} must be at most ${options.max}`, value);
29
28
  return null;
30
29
  }
@@ -0,0 +1,7 @@
1
+ import type { FieldDefinition, RuleOptions, ValidationContext, ValidationError } from "../types";
2
+ import { BaseValidationRule } from "../validationRule";
3
+ export declare class ObjectRule extends BaseValidationRule<Record<string, unknown>> {
4
+ private readonly objectSchema;
5
+ constructor(objectSchema: Map<string, FieldDefinition>);
6
+ validate(context: ValidationContext, options?: RuleOptions): ValidationError | null;
7
+ }
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ObjectRule = void 0;
4
+ const validator_1 = require("../validator");
5
+ const validationRule_1 = require("../validationRule");
6
+ class ObjectRule extends validationRule_1.BaseValidationRule {
7
+ objectSchema;
8
+ constructor(objectSchema) {
9
+ super("object");
10
+ this.objectSchema = objectSchema;
11
+ }
12
+ validate(context, options) {
13
+ const { field, value } = context;
14
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
15
+ return this.createError(field, options?.message || `${field} must be an object`, value);
16
+ }
17
+ const nestedResult = validator_1.Validator.validate(value, this.objectSchema);
18
+ if (nestedResult.errors.length === 0)
19
+ return null;
20
+ const firstError = nestedResult.errors[0];
21
+ return {
22
+ ...firstError,
23
+ field: `${field}.${firstError.field}`,
24
+ };
25
+ }
26
+ }
27
+ exports.ObjectRule = ObjectRule;
@@ -4,14 +4,14 @@ export interface StringRuleOptions extends RuleOptions {
4
4
  isEmpty?: boolean;
5
5
  minLength?: number;
6
6
  maxLength?: number;
7
- pattern?: RegExp;
7
+ length?: number;
8
8
  }
9
9
  /**
10
10
  * String validation rule.
11
11
  *
12
12
  * Validates that a value is a string.
13
13
  */
14
- export declare class StringRule extends BaseValidationRule {
14
+ export declare class StringRule extends BaseValidationRule<string> {
15
15
  constructor();
16
16
  validate(context: ValidationContext, options?: StringRuleOptions): ValidationError | null;
17
17
  /**
@@ -46,6 +46,7 @@ export declare class StringRule extends BaseValidationRule {
46
46
  * validator.string().uuid()
47
47
  */
48
48
  uuid(message?: string): this;
49
+ regex(pattern: RegExp, message?: string): this;
49
50
  }
50
51
  /**
51
52
  * Email validation rule.
@@ -72,3 +73,11 @@ export declare class UuidRule extends BaseValidationRule {
72
73
  constructor();
73
74
  validate(context: ValidationContext, options?: RuleOptions): ValidationError | null;
74
75
  }
76
+ /**
77
+ * UUID validation rule
78
+ */
79
+ export declare class RegExRule extends BaseValidationRule {
80
+ private readonly regex;
81
+ constructor(pattern: RegExp);
82
+ validate(context: ValidationContext, options?: RuleOptions): ValidationError | null;
83
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.UuidRule = exports.UrlRule = exports.EmailRule = exports.StringRule = void 0;
3
+ exports.RegExRule = exports.UuidRule = exports.UrlRule = exports.EmailRule = exports.StringRule = void 0;
4
4
  const validationRule_1 = require("../validationRule");
5
5
  /**
6
6
  * String validation rule.
@@ -15,15 +15,14 @@ class StringRule extends validationRule_1.BaseValidationRule {
15
15
  const { field, value } = context;
16
16
  if (typeof value !== "string")
17
17
  return this.createError(field, options?.message || `${field} must be a string`, value);
18
- let str = value;
19
- if (options?.isEmpty && str === "")
18
+ if (options?.isEmpty && value === "")
20
19
  return this.createError(field, `${field} cannot be empty`, value);
21
- if (options?.minLength && str.length < options.minLength)
20
+ if (options?.minLength && value.length < options.minLength)
22
21
  return this.createError(field, `${field} must be at least ${options.minLength} characters`, value);
23
- if (options?.maxLength && str.length > options.maxLength)
22
+ if (options?.maxLength && value.length > options.maxLength)
24
23
  return this.createError(field, `${field} must be at most ${options.maxLength} characters`, value);
25
- if (options?.pattern && !options.pattern.test(str))
26
- return this.createError(field, options?.message || `${field} format is invalid`, value);
24
+ if (options?.length && value.length !== options.length)
25
+ return this.createError(field, `${field} must be exactly ${options.length} characters`, value);
27
26
  return null;
28
27
  }
29
28
  /**
@@ -76,6 +75,13 @@ class StringRule extends validationRule_1.BaseValidationRule {
76
75
  });
77
76
  return this;
78
77
  }
78
+ regex(pattern, message) {
79
+ this.rules.push({
80
+ rule: new RegExRule(pattern),
81
+ options: { message },
82
+ });
83
+ return this;
84
+ }
79
85
  }
80
86
  exports.StringRule = StringRule;
81
87
  /**
@@ -132,3 +138,21 @@ class UuidRule extends validationRule_1.BaseValidationRule {
132
138
  }
133
139
  }
134
140
  exports.UuidRule = UuidRule;
141
+ /**
142
+ * UUID validation rule
143
+ */
144
+ class RegExRule extends validationRule_1.BaseValidationRule {
145
+ regex;
146
+ constructor(pattern) {
147
+ super("regex");
148
+ this.regex = pattern;
149
+ }
150
+ validate(context, options) {
151
+ const { field, value } = context;
152
+ if (!this.regex.test(value)) {
153
+ return this.createError(field, options?.message || `${field} format is invalid`, value);
154
+ }
155
+ return null;
156
+ }
157
+ }
158
+ exports.RegExRule = RegExRule;
@@ -1,11 +1,7 @@
1
1
  import type { RuleOptions, ValidationContext, ValidationError } from "../types";
2
2
  import { BaseValidationRule } from "../validationRule";
3
- /**
4
- * Required field validation rule.
5
- *
6
- * Validates that a value is present and not empty.
7
- */
8
- export declare class RequiredRule extends BaseValidationRule {
9
- constructor();
3
+ export declare class UnionRule extends BaseValidationRule {
4
+ private readonly unionRules;
5
+ constructor(unionRules: BaseValidationRule[]);
10
6
  validate(context: ValidationContext, options?: RuleOptions): ValidationError | null;
11
7
  }
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UnionRule = void 0;
4
+ const validationRule_1 = require("../validationRule");
5
+ class UnionRule extends validationRule_1.BaseValidationRule {
6
+ unionRules;
7
+ constructor(unionRules) {
8
+ super("union");
9
+ this.unionRules = unionRules;
10
+ }
11
+ validate(context, options) {
12
+ const { field, value } = context;
13
+ for (const unionRule of this.unionRules) {
14
+ const rules = unionRule.rules.length
15
+ ? unionRule.rules
16
+ : [{ rule: unionRule, options: undefined }];
17
+ let hasError = false;
18
+ for (const { rule, options: ruleOptions } of rules) {
19
+ const error = rule.validate({ field, value }, ruleOptions);
20
+ if (error) {
21
+ hasError = true;
22
+ break;
23
+ }
24
+ }
25
+ if (!hasError)
26
+ return null;
27
+ }
28
+ return this.createError(field, options?.message || `${field} must match at least one union rule`, value);
29
+ }
30
+ }
31
+ exports.UnionRule = UnionRule;
@@ -16,12 +16,10 @@ export interface ValidationError {
16
16
  * Full validation result.
17
17
  */
18
18
  export interface ValidationResult {
19
- /** Indicates whether the validation passed */
20
- valid: boolean;
21
19
  /** List of validation errors */
22
20
  errors: ValidationError[];
23
21
  /** Optional validated data */
24
- data?: unknown;
22
+ data: Record<string, unknown>;
25
23
  }
26
24
  /**
27
25
  * Options for a validation rule.
@@ -40,8 +38,6 @@ export interface ValidationContext {
40
38
  field: string;
41
39
  /** Field value */
42
40
  value: unknown;
43
- /** Full input data */
44
- data: unknown;
45
41
  }
46
42
  /**
47
43
  * Field definition used by the validation engine.
@@ -52,4 +48,5 @@ export interface FieldDefinition {
52
48
  options?: RuleOptions;
53
49
  }>;
54
50
  optional: boolean;
51
+ defaultValue?: unknown;
55
52
  }
@@ -8,10 +8,6 @@ import type { RuleOptions, ValidationContext, ValidationError } from "./types";
8
8
  * A rule must only evaluate the value and return an error when validation fails.
9
9
  */
10
10
  export interface ValidationRule {
11
- /**
12
- * Unique rule name.
13
- */
14
- readonly name: string;
15
11
  /**
16
12
  * Executes the rule validation.
17
13
  *
@@ -26,30 +22,25 @@ export interface ValidationRule {
26
22
  /**
27
23
  * Abstract base class for implementing validation rules.
28
24
  */
29
- export declare abstract class BaseValidationRule implements ValidationRule {
30
- readonly name: string;
25
+ export declare abstract class BaseValidationRule<T = any> implements ValidationRule {
26
+ private readonly name;
31
27
  readonly rules: Array<{
32
28
  rule: ValidationRule;
33
29
  options?: RuleOptions;
34
30
  }>;
35
- _optional: boolean;
31
+ hasOptional: boolean;
32
+ defaultValue?: T;
36
33
  constructor(name: string, rules?: Array<{
37
34
  rule: ValidationRule;
38
35
  options?: RuleOptions;
39
36
  }>);
40
37
  abstract validate(context: ValidationContext, options?: RuleOptions): ValidationError | null;
41
- /**
42
- * Marks the field as required.
43
- *
44
- * @param message - Optional custom error message
45
- * @returns This rule instance for chaining
46
- */
47
- required(message?: string): this;
48
38
  /**
49
39
  * Marks the field as optional.
50
40
  *
51
41
  * @returns This rule instance for chaining
52
42
  */
53
43
  optional(): this;
44
+ default(value: T): this;
54
45
  protected createError(field: string, message: string, value?: unknown): ValidationError;
55
46
  }
@@ -1,36 +1,30 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BaseValidationRule = void 0;
4
- const rules_1 = require("./rules");
5
4
  /**
6
5
  * Abstract base class for implementing validation rules.
7
6
  */
8
7
  class BaseValidationRule {
9
8
  name;
10
9
  rules;
11
- _optional = false;
10
+ hasOptional = false;
11
+ defaultValue = undefined;
12
12
  constructor(name, rules = []) {
13
13
  this.name = name;
14
14
  this.rules = rules;
15
15
  }
16
- /**
17
- * Marks the field as required.
18
- *
19
- * @param message - Optional custom error message
20
- * @returns This rule instance for chaining
21
- */
22
- required(message) {
23
- this._optional = false;
24
- this.rules.push({ rule: new rules_1.RequiredRule(), options: { message } });
25
- return this;
26
- }
27
16
  /**
28
17
  * Marks the field as optional.
29
18
  *
30
19
  * @returns This rule instance for chaining
31
20
  */
32
21
  optional() {
33
- this._optional = true;
22
+ this.hasOptional = true;
23
+ return this;
24
+ }
25
+ default(value) {
26
+ this.defaultValue = value;
27
+ this.hasOptional = true;
34
28
  return this;
35
29
  }
36
30
  createError(field, message, value) {
@@ -1,6 +1,6 @@
1
- import type { FieldDefinition } from "./types";
1
+ import type { FieldDefinition, RuleOptions } from "./types";
2
2
  import type { BaseValidationRule } from "./validationRule";
3
- import { BooleanRule, DateRule, type DateRuleOptions, NumberRule, type NumberRuleOptions, StringRule, type StringRuleOptions, ArrayRule, type ArrayRuleOptions, LiteralRule } from "./rules";
3
+ import { BooleanRule, DateRule, type DateRuleOptions, NumberRule, type NumberRuleOptions, StringRule, type StringRuleOptions, ArrayRule, type ArrayRuleOptions, LiteralRule, ObjectRule, BigIntRule, UnionRule } from "./rules";
4
4
  /**
5
5
  * Main validator class - provides factory methods for creating validators
6
6
  */
@@ -25,6 +25,16 @@ declare class Validator {
25
25
  * validator.number({ min: 18, max: 65 })
26
26
  */
27
27
  number(options?: NumberRuleOptions): NumberRule;
28
+ /**
29
+ * Creates a bigint validator.
30
+ *
31
+ * @param message - Optional custom error message
32
+ * @returns BigIntRule instance
33
+ *
34
+ * @example
35
+ * validator.bigint()
36
+ */
37
+ bigint(message?: string): BigIntRule;
28
38
  /**
29
39
  * Creates a boolean validator
30
40
  *
@@ -32,7 +42,7 @@ declare class Validator {
32
42
  * @returns BooleanValidator instance
33
43
  *
34
44
  * @example
35
- * validator.boolean().required()
45
+ * validator.boolean()
36
46
  */
37
47
  boolean(message?: string): BooleanRule;
38
48
  /**
@@ -54,32 +64,60 @@ declare class Validator {
54
64
  * @example
55
65
  * validator.array({ minLength: 1 }).string()
56
66
  */
57
- array(options?: ArrayRuleOptions): ArrayRule;
67
+ array(typeValid?: BaseValidationRule, options?: ArrayRuleOptions): ArrayRule;
58
68
  /**
59
- * Creates a literal value validator
69
+ * Creates an object validator with a nested schema.
60
70
  *
61
- * @param value - The literal value to validate against
62
- * @param message - Optional custom error message
63
- * @returns LiteralRule instance
71
+ * @param objectSchemaDefinition - Record mapping field names to their validators
72
+ * @param options - Optional rule options (message, etc.)
73
+ * @returns ObjectRule instance
64
74
  *
65
75
  * @example
66
- * validator.literal("admin").required()
76
+ * validator.object({
77
+ * id: validator.number(),
78
+ * name: validator.string({ maxLength: 100 })
79
+ * })
67
80
  */
68
- literal(value: unknown, message?: string): LiteralRule;
81
+ object(objectSchemaDefinition: Record<string, BaseValidationRule>, options?: RuleOptions): ObjectRule;
69
82
  /**
70
- * Creates a validation schema from a field definition object
83
+ * Creates a union validator that passes if any of the supplied rules pass.
71
84
  *
72
- * @param schemaDefinition - Object mapping field names to validators
73
- * @returns ValidationSchema instance
85
+ * @param unionRules - Array of validation rules to try in order
86
+ * @param options - Optional rule options (message, etc.)
87
+ * @returns UnionRule instance
74
88
  *
75
89
  * @example
76
- * const userSchema = validator.schema({
77
- * name: validator.string({ maxLength: 60 }),
78
- * email: validator.email().required(),
79
- * age: validator.number({ min: 18 })
80
- * })
90
+ * validator.union([
91
+ * validator.string(),
92
+ * validator.number()
93
+ * ])
81
94
  */
82
- schema(schemaDefinition: Record<string, BaseValidationRule>): Map<string, FieldDefinition>;
95
+ union(unionRules: BaseValidationRule[], options?: RuleOptions): UnionRule;
96
+ /**
97
+ * Creates a literal value validator
98
+ *
99
+ * @param value - The literal value to validate against
100
+ * @param message - Optional custom error message
101
+ * @returns LiteralRule instance
102
+ *
103
+ * @example
104
+ * validator.literal("admin")
105
+ */
106
+ literal(value: unknown, message?: string): LiteralRule;
83
107
  }
84
- export declare const validator: Validator;
108
+ /**
109
+ * Creates a validation schema from a field definition object
110
+ *
111
+ * @param schemaDefinition - Object mapping field names to validators
112
+ * @returns ValidationSchema instance
113
+ *
114
+ * @example
115
+ * const userSchema = validator.schema({
116
+ * name: validator.string({ maxLength: 60 }),
117
+ * email: validator.email(),
118
+ * age: validator.number({ min: 18 })
119
+ * })
120
+ */
121
+ export declare const schema: (schemaDefinition: Record<string, BaseValidationRule>) => Map<string, FieldDefinition>;
122
+ export declare const v: Validator;
85
123
  export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.validator = void 0;
3
+ exports.v = exports.schema = void 0;
4
4
  const rules_1 = require("./rules");
5
5
  /**
6
6
  * Main validator class - provides factory methods for creating validators
@@ -34,6 +34,20 @@ class Validator {
34
34
  numberRule.rules.push({ rule: numberRule, options });
35
35
  return numberRule;
36
36
  }
37
+ /**
38
+ * Creates a bigint validator.
39
+ *
40
+ * @param message - Optional custom error message
41
+ * @returns BigIntRule instance
42
+ *
43
+ * @example
44
+ * validator.bigint()
45
+ */
46
+ bigint(message) {
47
+ const bigIntRule = new rules_1.BigIntRule();
48
+ bigIntRule.rules.push({ rule: bigIntRule, options: { message } });
49
+ return bigIntRule;
50
+ }
37
51
  /**
38
52
  * Creates a boolean validator
39
53
  *
@@ -41,7 +55,7 @@ class Validator {
41
55
  * @returns BooleanValidator instance
42
56
  *
43
57
  * @example
44
- * validator.boolean().required()
58
+ * validator.boolean()
45
59
  */
46
60
  boolean(message) {
47
61
  const booleanRule = new rules_1.BooleanRule();
@@ -71,11 +85,48 @@ class Validator {
71
85
  * @example
72
86
  * validator.array({ minLength: 1 }).string()
73
87
  */
74
- array(options) {
75
- const arrayRule = new rules_1.ArrayRule();
88
+ array(typeValid, options) {
89
+ const arrayRule = new rules_1.ArrayRule(typeValid);
76
90
  arrayRule.rules.push({ rule: arrayRule, options });
77
91
  return arrayRule;
78
92
  }
93
+ /**
94
+ * Creates an object validator with a nested schema.
95
+ *
96
+ * @param objectSchemaDefinition - Record mapping field names to their validators
97
+ * @param options - Optional rule options (message, etc.)
98
+ * @returns ObjectRule instance
99
+ *
100
+ * @example
101
+ * validator.object({
102
+ * id: validator.number(),
103
+ * name: validator.string({ maxLength: 100 })
104
+ * })
105
+ */
106
+ object(objectSchemaDefinition, options) {
107
+ const objectSchema = (0, exports.schema)(objectSchemaDefinition);
108
+ const objectRule = new rules_1.ObjectRule(objectSchema);
109
+ objectRule.rules.push({ rule: objectRule, options });
110
+ return objectRule;
111
+ }
112
+ /**
113
+ * Creates a union validator that passes if any of the supplied rules pass.
114
+ *
115
+ * @param unionRules - Array of validation rules to try in order
116
+ * @param options - Optional rule options (message, etc.)
117
+ * @returns UnionRule instance
118
+ *
119
+ * @example
120
+ * validator.union([
121
+ * validator.string(),
122
+ * validator.number()
123
+ * ])
124
+ */
125
+ union(unionRules, options) {
126
+ const unionRule = new rules_1.UnionRule(unionRules);
127
+ unionRule.rules.push({ rule: unionRule, options });
128
+ return unionRule;
129
+ }
79
130
  /**
80
131
  * Creates a literal value validator
81
132
  *
@@ -84,7 +135,7 @@ class Validator {
84
135
  * @returns LiteralRule instance
85
136
  *
86
137
  * @example
87
- * validator.literal("admin").required()
138
+ * validator.literal("admin")
88
139
  */
89
140
  literal(value, message) {
90
141
  const literalRule = new rules_1.LiteralRule(value);
@@ -94,29 +145,6 @@ class Validator {
94
145
  });
95
146
  return literalRule;
96
147
  }
97
- /**
98
- * Creates a validation schema from a field definition object
99
- *
100
- * @param schemaDefinition - Object mapping field names to validators
101
- * @returns ValidationSchema instance
102
- *
103
- * @example
104
- * const userSchema = validator.schema({
105
- * name: validator.string({ maxLength: 60 }),
106
- * email: validator.email().required(),
107
- * age: validator.number({ min: 18 })
108
- * })
109
- */
110
- schema(schemaDefinition) {
111
- const schema = new ValidationSchema();
112
- for (const [fieldName, validator] of Object.entries(schemaDefinition)) {
113
- schema.addField(fieldName, {
114
- rules: validator.rules,
115
- optional: validator._optional,
116
- });
117
- }
118
- return schema.build();
119
- }
120
148
  }
121
149
  /**
122
150
  * ValidationSchema - Internal representation of validation rules
@@ -145,23 +173,31 @@ class ValidationSchema {
145
173
  build() {
146
174
  return this.fields;
147
175
  }
148
- /**
149
- * Gets a specific field definition
150
- *
151
- * @param name - Field name
152
- * @returns Field definition or undefined
153
- */
154
- getField(name) {
155
- return this.fields.get(name);
156
- }
157
- /**
158
- * Gets all field names in the schema
159
- *
160
- * @returns Array of field names
161
- */
162
- getFieldNames() {
163
- return Array.from(this.fields.keys());
164
- }
165
176
  }
177
+ /**
178
+ * Creates a validation schema from a field definition object
179
+ *
180
+ * @param schemaDefinition - Object mapping field names to validators
181
+ * @returns ValidationSchema instance
182
+ *
183
+ * @example
184
+ * const userSchema = validator.schema({
185
+ * name: validator.string({ maxLength: 60 }),
186
+ * email: validator.email(),
187
+ * age: validator.number({ min: 18 })
188
+ * })
189
+ */
190
+ const schema = (schemaDefinition) => {
191
+ const schema = new ValidationSchema();
192
+ for (const [fieldName, validator] of Object.entries(schemaDefinition)) {
193
+ schema.addField(fieldName, {
194
+ rules: validator.rules,
195
+ optional: validator.hasOptional,
196
+ defaultValue: validator.defaultValue,
197
+ });
198
+ }
199
+ return schema.build();
200
+ };
201
+ exports.schema = schema;
166
202
  // Export a singleton instance for convenience
167
- exports.validator = new Validator();
203
+ exports.v = new Validator();
@@ -47,9 +47,9 @@ export declare class Validator {
47
47
  * @throws {ValidationException} When validation fails
48
48
  *
49
49
  * @example
50
- * const schema = ValidationSchema.create()
51
- * .field("email").required().string().email()
52
- * .build();
50
+ * const schema = schema({
51
+ * email: validator.string().emaiil().required()
52
+ * })
53
53
  *
54
54
  * const data = Validator.validateOrFail({ email: "a@b.com" }, schema);
55
55
  * // `data` is the original input when valid
@@ -43,14 +43,20 @@ class Validator {
43
43
  static validate(data, schema) {
44
44
  const errors = [];
45
45
  for (const [fieldName, fieldDef] of schema.entries()) {
46
- const value = data[fieldName];
47
46
  const context = {
48
47
  field: fieldName,
49
- value,
50
- data,
48
+ value: data[fieldName],
51
49
  };
52
- if (fieldDef.optional && value === undefined)
50
+ if (fieldDef.optional && data[fieldName] === undefined)
53
51
  continue;
52
+ if (data[fieldName] === undefined || data[fieldName] === null) {
53
+ errors.push({
54
+ field: fieldName,
55
+ message: `${fieldName} is required`,
56
+ rule: "required",
57
+ });
58
+ continue;
59
+ }
54
60
  for (const { rule, options } of fieldDef.rules) {
55
61
  const error = rule.validate(context, options);
56
62
  if (error) {
@@ -60,7 +66,6 @@ class Validator {
60
66
  }
61
67
  }
62
68
  return {
63
- valid: errors.length === 0,
64
69
  errors,
65
70
  data: errors.length === 0 ? data : undefined,
66
71
  };
@@ -74,17 +79,21 @@ class Validator {
74
79
  * @throws {ValidationException} When validation fails
75
80
  *
76
81
  * @example
77
- * const schema = ValidationSchema.create()
78
- * .field("email").required().string().email()
79
- * .build();
82
+ * const schema = schema({
83
+ * email: validator.string().emaiil().required()
84
+ * })
80
85
  *
81
86
  * const data = Validator.validateOrFail({ email: "a@b.com" }, schema);
82
87
  * // `data` is the original input when valid
83
88
  */
84
89
  static validateOrFail(data, schema) {
85
90
  const result = this.validate(data, schema);
86
- if (!result.valid)
91
+ if (result.errors.length !== 0)
87
92
  throw new validationException_1.ValidationException(result.errors);
93
+ for (const [fieldName, fieldDef] of schema.entries()) {
94
+ if (!(fieldName in result) && fieldDef.defaultValue !== undefined)
95
+ result.data[fieldName] = fieldDef.defaultValue;
96
+ }
88
97
  return result.data;
89
98
  }
90
99
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skyguard-js",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "A lightweight, dependency-free TypeScript backend framework",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -72,19 +72,19 @@
72
72
  },
73
73
  "license": "MIT",
74
74
  "devDependencies": {
75
- "@eslint/js": "9.39.2",
75
+ "@eslint/js": "10.0.1",
76
76
  "@jest/globals": "30.2.0",
77
77
  "@types/jest": "30.0.0",
78
- "@types/node": "25.0.8",
79
- "eslint": "9.39.2",
78
+ "@types/node": "25.3.0",
79
+ "eslint": "10.0.2",
80
80
  "eslint-config-prettier": "10.1.8",
81
- "globals": "17.2.0",
81
+ "globals": "17.3.0",
82
82
  "jest": "30.2.0",
83
- "nodemon": "3.1.11",
83
+ "nodemon": "3.1.14",
84
84
  "prettier": "3.8.1",
85
85
  "ts-jest": "29.4.6",
86
86
  "ts-node": "10.9.2",
87
87
  "typescript": "5.9.3",
88
- "typescript-eslint": "8.53.0"
88
+ "typescript-eslint": "8.56.1"
89
89
  }
90
90
  }
@@ -1,21 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RequiredRule = void 0;
4
- const validationRule_1 = require("../validationRule");
5
- /**
6
- * Required field validation rule.
7
- *
8
- * Validates that a value is present and not empty.
9
- */
10
- class RequiredRule extends validationRule_1.BaseValidationRule {
11
- constructor() {
12
- super("required");
13
- }
14
- validate(context, options) {
15
- const { field, value } = context;
16
- if (value === null || value === undefined)
17
- return this.createError(field, options?.message || `${field} is required`, value);
18
- return null;
19
- }
20
- }
21
- exports.RequiredRule = RequiredRule;