@anjianshi/utils 1.2.7 → 1.3.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.
Files changed (55) hide show
  1. package/env-node/env-reader.d.ts +12 -0
  2. package/env-node/env-reader.js +31 -0
  3. package/env-node/{logging.d.ts → logging/handlers.d.ts} +1 -6
  4. package/env-node/{logging.js → logging/handlers.js} +1 -12
  5. package/env-node/logging/index.d.ts +11 -0
  6. package/env-node/logging/index.js +14 -0
  7. package/env-node/typeorm/adapt-logging.d.ts +11 -0
  8. package/env-node/typeorm/adapt-logging.js +42 -0
  9. package/env-node/typeorm/index.d.ts +31 -0
  10. package/env-node/typeorm/index.js +39 -0
  11. package/lang/index.d.ts +1 -0
  12. package/lang/index.js +1 -0
  13. package/lang/may-success.d.ts +40 -0
  14. package/lang/may-success.js +27 -0
  15. package/lang/types.d.ts +12 -43
  16. package/lang/types.js +0 -13
  17. package/md5.d.ts +30 -0
  18. package/md5.js +309 -0
  19. package/package.json +3 -1
  20. package/src/env-node/env-reader.ts +33 -0
  21. package/src/env-node/{logging.ts → logging/handlers.ts} +1 -21
  22. package/src/env-node/logging/index.ts +16 -0
  23. package/src/env-node/typeorm/adapt-logging.ts +54 -0
  24. package/src/env-node/typeorm/index.ts +62 -0
  25. package/src/lang/index.ts +1 -0
  26. package/src/lang/may-success.ts +57 -0
  27. package/src/lang/types.ts +14 -59
  28. package/src/md5.ts +319 -0
  29. package/src/url.ts +48 -55
  30. package/src/validators/array.ts +62 -0
  31. package/src/validators/base.ts +49 -0
  32. package/src/validators/boolean.ts +24 -0
  33. package/src/validators/factories.ts +47 -0
  34. package/src/validators/index.ts +9 -0
  35. package/src/validators/number.ts +43 -0
  36. package/src/validators/object.ts +70 -0
  37. package/src/validators/string.ts +55 -0
  38. package/url.d.ts +31 -15
  39. package/url.js +22 -28
  40. package/validators/array.d.ts +20 -0
  41. package/validators/array.js +44 -0
  42. package/validators/base.d.ts +26 -0
  43. package/validators/base.js +28 -0
  44. package/validators/boolean.d.ts +4 -0
  45. package/validators/boolean.js +28 -0
  46. package/validators/factories.d.ts +18 -0
  47. package/validators/factories.js +41 -0
  48. package/validators/index.d.ts +7 -0
  49. package/validators/index.js +7 -0
  50. package/validators/number.d.ts +15 -0
  51. package/validators/number.js +32 -0
  52. package/validators/object.d.ts +20 -0
  53. package/validators/object.js +52 -0
  54. package/validators/string.d.ts +17 -0
  55. package/validators/string.js +35 -0
@@ -0,0 +1,49 @@
1
+ import { success, failed, type MaySuccess } from '../lang/index.js'
2
+
3
+ export interface BaseOptions {
4
+ /**
5
+ * 是否允许 null 值
6
+ * @default false
7
+ */
8
+ null: boolean
9
+
10
+ /**
11
+ * 字段是否必须有值(不能是 undefined)
12
+ * @default true
13
+ */
14
+ required: boolean
15
+
16
+ /**
17
+ * 默认值,字段无值(或值为 undefined)时生效,值为 null 不会生效。
18
+ * 指定后 required 选项将失去作用。
19
+ */
20
+ defaults: unknown
21
+ }
22
+
23
+ export class Validator<ExtraOptions = unknown> {
24
+ readonly options: BaseOptions & ExtraOptions
25
+
26
+ constructor(options: Partial<BaseOptions> & ExtraOptions) {
27
+ this.options = {
28
+ null: false,
29
+ required: true,
30
+ defaults: undefined,
31
+ ...options,
32
+ }
33
+ }
34
+
35
+ /**
36
+ * 各子类继承此方法补充验证逻辑
37
+ */
38
+ validate(field: string, value: unknown): MaySuccess<unknown> {
39
+ if (typeof value === 'undefined') {
40
+ if (typeof this.options.defaults !== 'undefined') {
41
+ value = this.options.defaults
42
+ } else if (this.options.required) {
43
+ return failed(`${field} is required`)
44
+ }
45
+ }
46
+ if (value === null && !this.options.null) return failed(`${field} cannot be null`)
47
+ return success(value)
48
+ }
49
+ }
@@ -0,0 +1,24 @@
1
+ import { success, failed } from '../lang/index.js'
2
+ import { Validator } from './base.js'
3
+
4
+ export class BooleanValidator extends Validator {
5
+ validate(field: string, value: unknown) {
6
+ const superResult = super.validate(field, value)
7
+ if (!superResult.success) return superResult
8
+
9
+ value = superResult.data
10
+ if (value === null || value === undefined) return superResult
11
+
12
+ if (typeof value === 'string') {
13
+ const str = value.trim().toLowerCase()
14
+ if (['1', 'true', 'on', 'yes'].includes(str)) value = true
15
+ else if (['0', 'false', 'off', 'no'].includes(str)) value = false
16
+ } else if (typeof value === 'number') {
17
+ if (value === 1) value = true
18
+ else if (value === 0) value = false
19
+ }
20
+
21
+ if (typeof value !== 'boolean') return failed(`${field} must be true or false`)
22
+ return success(value)
23
+ }
24
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * 创建 validator 的快捷方式
3
+ */
4
+ import { ArrayValidator, type ArrayOptions } from './array.js'
5
+ import { Validator, type BaseOptions } from './base.js'
6
+ import { BooleanValidator } from './boolean.js'
7
+ import { NumberValidator } from './number.js'
8
+ import { ObjectValidator, type RecordOptions } from './object.js'
9
+ import { StringValidator } from './string.js'
10
+
11
+ /** 仅进行基本检查(如检查空值),不检查具体格式 */
12
+ export function any(options?: Partial<BaseOptions>) {
13
+ return new Validator(options ?? {})
14
+ }
15
+
16
+ export function string(...args: ConstructorParameters<typeof StringValidator>) {
17
+ return new StringValidator(...args)
18
+ }
19
+
20
+ export function number(...args: ConstructorParameters<typeof NumberValidator>) {
21
+ return new NumberValidator(...args)
22
+ }
23
+
24
+ export function boolean(options: Partial<BaseOptions> = {}) {
25
+ return new BooleanValidator(options)
26
+ }
27
+
28
+ export function array(options: Validator | (ArrayOptions & Partial<BaseOptions>)) {
29
+ if (options instanceof Validator) options = { item: options }
30
+ return new ArrayValidator(options)
31
+ }
32
+
33
+ export function tuple(validators: Validator[], baseOptions?: Partial<BaseOptions>) {
34
+ return new ArrayValidator({
35
+ ...(baseOptions ?? {}),
36
+ tuple: validators,
37
+ })
38
+ }
39
+
40
+ export function struct(validators: Record<string, Validator>, options: Partial<BaseOptions> = {}) {
41
+ return new ObjectValidator({ ...options, struct: validators })
42
+ }
43
+
44
+ export function record(options: Validator | (RecordOptions & Partial<BaseOptions>)) {
45
+ if (options instanceof Validator) options = { record: options }
46
+ return new ObjectValidator(options)
47
+ }
@@ -0,0 +1,9 @@
1
+ export * from './base.js'
2
+
3
+ export * from './array.js'
4
+ export * from './boolean.js'
5
+ export * from './number.js'
6
+ export * from './object.js'
7
+ export * from './string.js'
8
+
9
+ export * as validators from './factories.js'
@@ -0,0 +1,43 @@
1
+ import { success, failed } from '../lang/index.js'
2
+ import { type BaseOptions, Validator } from './base.js'
3
+
4
+ export interface NumberOptions {
5
+ /** 数值最小值 */
6
+ min?: number
7
+ /** 数值最大值 */
8
+ max?: number
9
+ /** 是否允许小数 @default false */
10
+ float: boolean
11
+ /** 指定可选值 */
12
+ enum?: number[]
13
+ }
14
+
15
+ export class NumberValidator extends Validator<NumberOptions> {
16
+ constructor(options: Partial<BaseOptions & NumberOptions> = {}) {
17
+ super({
18
+ float: false,
19
+ ...options,
20
+ })
21
+ }
22
+
23
+ validate(field: string, value: unknown) {
24
+ const superResult = super.validate(field, value)
25
+ if (!superResult.success) return superResult
26
+
27
+ value = superResult.data
28
+ if (value === null || value === undefined) return superResult
29
+ const opt = this.options
30
+
31
+ if (typeof value === 'string') value = parseFloat(value)
32
+
33
+ if (typeof value !== 'number' || !isFinite(value))
34
+ return failed(`${field} must be a valid number`)
35
+ if (opt.enum !== undefined && !opt.enum.includes(value))
36
+ return failed(`${field} can only be one of ${opt.enum.join(', ')}.`)
37
+ if (!opt.float && value % 1 !== 0) return failed(`${field} must be a integer`)
38
+ if (typeof opt.min === 'number' && value < opt.min) return failed(`${field} must >= ${opt.min}`)
39
+ if (typeof opt.max === 'number' && value > opt.max) return failed(`${field} must <= ${opt.max}`)
40
+
41
+ return success(value)
42
+ }
43
+ }
@@ -0,0 +1,70 @@
1
+ import isPlainObject from 'lodash/isPlainObject.js'
2
+ import { success, failed } from '../lang/index.js'
3
+ import { Validator } from './base.js'
4
+
5
+ /** 验证有明确键值对结构的对象 */
6
+ export type StructOptions = {
7
+ /** 定义对象结构,及各个值的验证规则 */
8
+ struct: Record<string, Validator>
9
+ }
10
+
11
+ /**
12
+ * 验证有任意多个 key,但值的类型固定的对象
13
+ */
14
+ export type RecordOptions = {
15
+ /** 验证单个值 */
16
+ record: Validator
17
+ /** 对象至少要有几项 */
18
+ min?: number
19
+ /** 对象最多有几项 */
20
+ max?: number
21
+ }
22
+
23
+ export class ObjectValidator extends Validator<StructOptions | RecordOptions> {
24
+ validate(field: string, value: unknown) {
25
+ const superResult = super.validate(field, value)
26
+ if (!superResult.success) return superResult
27
+
28
+ value = superResult.data
29
+ if (value === null || value === undefined) return superResult
30
+ const opt = this.options
31
+
32
+ if (!isPlainObject(value)) return failed(`${field} should be a plain object`)
33
+
34
+ const formatted: Record<string, unknown> = {}
35
+ if ('struct' in opt) {
36
+ for (const [key, itemValidator] of Object.entries(opt.struct)) {
37
+ const itemResult = itemValidator.validate(
38
+ `${field}["${key}"]`,
39
+ (value as Record<string, unknown>)[key]
40
+ )
41
+ if (itemResult.success) {
42
+ if (itemResult.data !== undefined) formatted[key] = itemResult.data
43
+ } else {
44
+ return itemResult
45
+ }
46
+ }
47
+ } else {
48
+ for (const [key, itemValue] of Object.entries(value as Record<string, unknown>)) {
49
+ // record 场景下,值为 undefined 的项目视为不存在,不保留在验证结果里,
50
+ // 不然一些因为不想赋值而填充了 undefined 值的项目可能意外触发验证失败,或意外得到了默认值。
51
+ // (因此 validator 的 required 选项和 defaults 选项也没有意义了)
52
+ if (itemValue === undefined) continue
53
+
54
+ const itemResult = opt.record.validate(`${field}["${key}"]`, itemValue)
55
+ if (itemResult.success) {
56
+ if (itemResult.data !== undefined) formatted[key] = itemResult.data
57
+ } else {
58
+ return itemResult
59
+ }
60
+ }
61
+
62
+ const length = Object.keys(formatted).length
63
+ if (typeof opt.min === 'number' && length < opt.min)
64
+ return failed(`size of ${field} should >= ${opt.min}`)
65
+ if (typeof opt.max === 'number' && length > opt.max)
66
+ return failed(`size of ${field} should <= ${opt.max}`)
67
+ }
68
+ return success(formatted)
69
+ }
70
+ }
@@ -0,0 +1,55 @@
1
+ import { success, failed } from '../lang/index.js'
2
+ import { type BaseOptions, Validator } from './base.js'
3
+
4
+ export interface StringOptions {
5
+ /** 字符串最小长度。defaults='' 时默认为 0,否则默认为 1 */
6
+ min: number
7
+ /** 字符串最大长度。 */
8
+ max?: number
9
+ /** 字符串需匹配此正则 */
10
+ pattern?: RegExp
11
+ /** 指定一个数组或 TypeScript enum,字段值必须在此 enum 之中 */
12
+ enum?: string[] | Record<string, string>
13
+ /** 验证之前,是否先清除两侧空白字符(默认开启) */
14
+ trim: boolean
15
+ }
16
+
17
+ export class StringValidator extends Validator<StringOptions> {
18
+ constructor(options: Partial<BaseOptions & StringOptions> = {}) {
19
+ super({
20
+ min: options.defaults === '' ? 0 : 1,
21
+ trim: true,
22
+ ...options,
23
+ })
24
+ }
25
+
26
+ validate(field: string, value: unknown) {
27
+ const opt = this.options
28
+
29
+ const superResult = super.validate(field, value)
30
+ if (!superResult.success) return superResult
31
+
32
+ value = superResult.data
33
+ if (value === null || value === undefined) return superResult
34
+ if (typeof value !== 'string') return failed(`${field} must be a string`)
35
+
36
+ const formatted = opt.trim ? value.trim() : value
37
+
38
+ if (typeof opt.min === 'number' && formatted.length < opt.min)
39
+ return failed(`${field}'s length must >= ${opt.min}`)
40
+
41
+ if (typeof opt.max === 'number' && formatted.length > opt.max)
42
+ return failed(`${field}'s length must <= ${opt.max}`)
43
+
44
+ if (opt.pattern && !opt.pattern.exec(formatted))
45
+ return failed(`${field} does not match the pattern.`)
46
+
47
+ if (opt.enum !== undefined) {
48
+ const validValues = Array.isArray(opt.enum) ? opt.enum : Object.values(opt.enum)
49
+ if (!validValues.includes(formatted))
50
+ return failed(`${field} can only be one of ${validValues.join(', ')}.`)
51
+ }
52
+
53
+ return success(formatted)
54
+ }
55
+ }
package/url.d.ts CHANGED
@@ -1,32 +1,48 @@
1
+ /**
2
+ * 从 URL 中解析出 query 对象
3
+ * 注意:不带 ? 号的纯 query 内容需手动加上 ? 再传入。
4
+ *
5
+ * [array]
6
+ * 是否把重复出现的 key 保存为数组(默认不开启)
7
+ * a=1&a=2 => { a: [1,2] }
8
+ *
9
+ * [loose]
10
+ * 是否开启“宽松模式”(默认不开启)
11
+ * 1. hash 里的内容也会被解析,以兼容拼接错误的 URL(把 query 拼到了 hash 后面)。
12
+ * 2. 出现多个 ? 符号时,会把 ? 也当做 & 分隔符(index.html?a=1&b=2?c=3)
13
+ *
14
+ * [decode]
15
+ * 是否对 query 值进行 decode(默认开启)
16
+ */
1
17
  declare function parseQuery(url: string, options?: {
2
18
  array?: false;
3
- strict?: boolean;
19
+ loose?: boolean;
20
+ decode?: boolean;
4
21
  }): Record<string, string>;
5
22
  declare function parseQuery(url: string, options: {
6
23
  array: true;
7
- strict?: boolean;
24
+ loose?: boolean;
25
+ decode?: boolean;
8
26
  }): Record<string, string | string[]>;
9
27
  export { parseQuery };
10
- /**
11
- * 取 query 中指定参数的值
12
- * - 参数存在,返回参数值(可能是空字符串);不存在返回 null
13
- * - 解析 query 固定基于 parseQuery() 的 { array: false, strict: false } 规则
14
- * - 和 parseQuery() 一样,url 可以根据需要传 location.href/search/hash
15
- */
16
- export declare function getQueryParam(name: string, url: string): string | null;
17
28
  /**
18
29
  * 把对象合并成 query string。
19
- * 支持字符串、数值、布尔值(不建议,考虑用 0 和 1 代替),数组会转换成 name[]=value 的格式
30
+ * - 支持字符串、数值、布尔值、数组。
31
+ * - 布尔值会替换成 0 和 1。
32
+ * - 数组会多次赋值:{ a: [1,2,3] } => 'a=1&a=2&a=3',不支持嵌套数组
33
+ * - encode 为 true 时会对 value 执行 encodeURIComponent(默认为 true)
20
34
  */
21
35
  type StringifyVal = string | number | boolean;
22
- export declare function stringifyQuery(obj: {
36
+ type StringifyQuery = {
23
37
  [key: string]: StringifyVal | StringifyVal[] | undefined;
24
- }): string;
38
+ };
39
+ export declare function stringifyQuery(obj: StringifyQuery, encode?: boolean): string;
25
40
  /**
26
41
  * 拆分 URL 的各个部分
27
42
  *
28
43
  * bare 为 true,则 search 不带 '?',hash 不带 '#'
29
44
  * 否则和 location.search / hash 一样
45
+ * (默认为 true)
30
46
  */
31
47
  export declare function splitUrl(url: string, bare?: boolean): {
32
48
  base: string;
@@ -34,12 +50,12 @@ export declare function splitUrl(url: string, bare?: boolean): {
34
50
  hash: string;
35
51
  };
36
52
  /**
37
- * 把指定 query 和 hash 内容合并到 url 上
53
+ * query 和 hash 内容合并到 url 上
38
54
  *
39
55
  * query object 与现有 search 合并,替换同名项(值为数组的,用新数组代替老的,不会合并数组)
40
- * hash string url 已有 hash,会用此值代替。带不带开头的 '?' 皆可。
56
+ * hash string 带不带开头的 '#' 皆可。会代替 url 已有的 hash
41
57
  */
42
- export declare function combineUrl(origUrl: string, query?: Record<string, string | string[]>, hash?: string): string;
58
+ export declare function combineUrl(origUrl: string, query?: StringifyQuery, hash?: string): string;
43
59
  /**
44
60
  * decodeURIComponent() 对于不规范编码的字符串可能会报错(例如字符串里出现了“%”)
45
61
  * 用此函数代替可避免此问题
package/url.js CHANGED
@@ -9,23 +9,20 @@
9
9
  */
10
10
  import isPlainObject from 'lodash/isPlainObject.js';
11
11
  function parseQuery(url, options) {
12
- if (!url)
12
+ const { array = false, loose = false, decode = true } = options ?? {};
13
+ // 正常状态下,将仅剩 a=1&b=1(即不会再有 ? 和 #);loose 模型下,可能为 a=1&b=2#c=3?d=4
14
+ const queryString = (loose ? /(\?|#)(.+)/ : /(\?)(.+?)(#|$)/).exec(url)?.[2] ?? '';
15
+ if (!queryString)
13
16
  return {};
14
- const { array = false, strict = false } = options ?? {};
15
- const queryString = strict
16
- ? (/^[^?#]*\?(.+?)(\?|#|$)/.exec(url) ?? ['', ''])[1] // 排除 hash、重复的 ? 符号
17
- : url;
18
17
  const query = {};
19
18
  const reg = /([^#?&]*)=([^#?&]*)/g;
20
19
  let re = reg.exec(queryString);
21
20
  while (re) {
22
- const [name, value] = [re[1], re[2]];
23
- if (name.endsWith('[]') && array) {
24
- const realName = name.slice(0, -2);
25
- if (Array.isArray(query[realName]))
26
- query[realName].push(value);
27
- else
28
- query[realName] = [value];
21
+ const [name, rawValue] = [re[1], re[2]];
22
+ const value = decode ? safeDecode(rawValue) : rawValue;
23
+ if (array && query[name] !== undefined) {
24
+ const prev = query[name];
25
+ query[name] = Array.isArray(prev) ? [...prev, value] : [prev, value];
29
26
  }
30
27
  else {
31
28
  query[name] = value;
@@ -35,28 +32,24 @@ function parseQuery(url, options) {
35
32
  return query;
36
33
  }
37
34
  export { parseQuery };
38
- /**
39
- * 取 query 中指定参数的值
40
- * - 参数存在,返回参数值(可能是空字符串);不存在返回 null
41
- * - 解析 query 固定基于 parseQuery() 的 { array: false, strict: false } 规则
42
- * - 和 parseQuery() 一样,url 可以根据需要传 location.href/search/hash
43
- */
44
- export function getQueryParam(name, url) {
45
- const query = parseQuery(url);
46
- return typeof query[name] === 'string' ? query[name] : null;
47
- }
48
- export function stringifyQuery(obj) {
35
+ export function stringifyQuery(obj, encode = true) {
49
36
  if (!isPlainObject(obj))
50
37
  return '';
51
38
  return (Object.entries(obj)
52
39
  // 过滤值为 undefined 的项目,使其完全不出现在最终的 query 中
53
40
  .filter((entry) => entry[1] !== undefined)
54
- .map(([name, value]) => stringifyQueryItem(name, value))
41
+ .map(([name, value]) => stringifyQueryItem(name, value, encode))
55
42
  .join('&'));
56
43
  }
57
- function stringifyQueryItem(name, value) {
44
+ function stringifyQueryItem(name, value, encode) {
58
45
  if (Array.isArray(value))
59
- return value.map(subValue => stringifyQueryItem(`${name}[]`, subValue)).join('&');
46
+ return value.map(subValue => stringifyQueryItem(name, subValue, encode)).join('&');
47
+ if (typeof value === 'boolean')
48
+ value = value ? '1' : '0';
49
+ if (typeof value === 'number')
50
+ value = value.toString();
51
+ if (encode)
52
+ value = encodeURIComponent(value);
60
53
  return `${name}=${value}`;
61
54
  }
62
55
  /**
@@ -64,6 +57,7 @@ function stringifyQueryItem(name, value) {
64
57
  *
65
58
  * bare 为 true,则 search 不带 '?',hash 不带 '#'
66
59
  * 否则和 location.search / hash 一样
60
+ * (默认为 true)
67
61
  */
68
62
  export function splitUrl(url, bare = true) {
69
63
  let hashIndex = url.indexOf('#');
@@ -81,10 +75,10 @@ export function splitUrl(url, bare = true) {
81
75
  };
82
76
  }
83
77
  /**
84
- * 把指定 query 和 hash 内容合并到 url 上
78
+ * query 和 hash 内容合并到 url 上
85
79
  *
86
80
  * query object 与现有 search 合并,替换同名项(值为数组的,用新数组代替老的,不会合并数组)
87
- * hash string url 已有 hash,会用此值代替。带不带开头的 '?' 皆可。
81
+ * hash string 带不带开头的 '#' 皆可。会代替 url 已有的 hash
88
82
  */
89
83
  export function combineUrl(origUrl, query = {}, hash = '') {
90
84
  if (hash.startsWith('#'))
@@ -0,0 +1,20 @@
1
+ import { Validator } from './base.js';
2
+ /** 验证元素数量任意、元素类型相同的数组 */
3
+ export type ArrayOptions = {
4
+ /** 验证数组各元素 */
5
+ item: Validator;
6
+ /** 数组最小长度 */
7
+ min?: number;
8
+ /** 数组最大长度 */
9
+ max?: number;
10
+ /** 是否对数组元素进行去重 @defaults false */
11
+ unique?: boolean;
12
+ };
13
+ /** 验证元素数量固定、类型可以不同的数组 */
14
+ export type TupleOptions = {
15
+ /** 验证数组各元素(validator 与元素一一对应) */
16
+ tuple: Validator[];
17
+ };
18
+ export declare class ArrayValidator extends Validator<ArrayOptions | TupleOptions> {
19
+ validate(field: string, value: unknown): import("../lang/may-success.js").Failed<void> | import("../lang/may-success.js").Success<unknown>;
20
+ }
@@ -0,0 +1,44 @@
1
+ import { success, failed } from '../lang/index.js';
2
+ import { Validator } from './base.js';
3
+ export class ArrayValidator extends Validator {
4
+ validate(field, value) {
5
+ const superResult = super.validate(field, value);
6
+ if (!superResult.success)
7
+ return superResult;
8
+ value = superResult.data;
9
+ if (value === null || value === undefined)
10
+ return superResult;
11
+ const opt = this.options;
12
+ if (!Array.isArray(value))
13
+ return failed(`${field} should be an array`);
14
+ let formatted = [];
15
+ if ('item' in opt) {
16
+ if (typeof opt.min === 'number' && value.length < opt.min)
17
+ return failed(`array ${field}'s length should >= ${opt.min}`);
18
+ if (typeof opt.max === 'number' && value.length > opt.max)
19
+ return failed(`array ${field}'s length should <= ${opt.max}`);
20
+ for (let i = 0; i < value.length; i++) {
21
+ const itemResult = opt.item.validate(`${field}[${i}]`, value[i]);
22
+ if (itemResult.success)
23
+ formatted.push(itemResult.data);
24
+ else
25
+ return itemResult;
26
+ }
27
+ if (opt.unique === true)
28
+ formatted = [...new Set(formatted)];
29
+ }
30
+ else {
31
+ if (value.length > opt.tuple.length)
32
+ return failed(`${field} should be a tuple with ${opt.tuple.length} items`);
33
+ // 这种情况不能遍历 value,因为它的长度可能小于 opt.tuple
34
+ for (let i = 0; i < opt.tuple.length; i++) {
35
+ const itemResult = opt.tuple[i].validate(`${field}[${i}]`, value[i]);
36
+ if (itemResult.success)
37
+ formatted.push(itemResult.data);
38
+ else
39
+ return itemResult;
40
+ }
41
+ }
42
+ return success(formatted);
43
+ }
44
+ }
@@ -0,0 +1,26 @@
1
+ import { type MaySuccess } from '../lang/index.js';
2
+ export interface BaseOptions {
3
+ /**
4
+ * 是否允许 null 值
5
+ * @default false
6
+ */
7
+ null: boolean;
8
+ /**
9
+ * 字段是否必须有值(不能是 undefined)
10
+ * @default true
11
+ */
12
+ required: boolean;
13
+ /**
14
+ * 默认值,字段无值(或值为 undefined)时生效,值为 null 不会生效。
15
+ * 指定后 required 选项将失去作用。
16
+ */
17
+ defaults: unknown;
18
+ }
19
+ export declare class Validator<ExtraOptions = unknown> {
20
+ readonly options: BaseOptions & ExtraOptions;
21
+ constructor(options: Partial<BaseOptions> & ExtraOptions);
22
+ /**
23
+ * 各子类继承此方法补充验证逻辑
24
+ */
25
+ validate(field: string, value: unknown): MaySuccess<unknown>;
26
+ }
@@ -0,0 +1,28 @@
1
+ import { success, failed } from '../lang/index.js';
2
+ export class Validator {
3
+ options;
4
+ constructor(options) {
5
+ this.options = {
6
+ null: false,
7
+ required: true,
8
+ defaults: undefined,
9
+ ...options,
10
+ };
11
+ }
12
+ /**
13
+ * 各子类继承此方法补充验证逻辑
14
+ */
15
+ validate(field, value) {
16
+ if (typeof value === 'undefined') {
17
+ if (typeof this.options.defaults !== 'undefined') {
18
+ value = this.options.defaults;
19
+ }
20
+ else if (this.options.required) {
21
+ return failed(`${field} is required`);
22
+ }
23
+ }
24
+ if (value === null && !this.options.null)
25
+ return failed(`${field} cannot be null`);
26
+ return success(value);
27
+ }
28
+ }
@@ -0,0 +1,4 @@
1
+ import { Validator } from './base.js';
2
+ export declare class BooleanValidator extends Validator {
3
+ validate(field: string, value: unknown): import("../lang/may-success.js").Failed<void> | import("../lang/may-success.js").Success<unknown>;
4
+ }
@@ -0,0 +1,28 @@
1
+ import { success, failed } from '../lang/index.js';
2
+ import { Validator } from './base.js';
3
+ export class BooleanValidator extends Validator {
4
+ validate(field, value) {
5
+ const superResult = super.validate(field, value);
6
+ if (!superResult.success)
7
+ return superResult;
8
+ value = superResult.data;
9
+ if (value === null || value === undefined)
10
+ return superResult;
11
+ if (typeof value === 'string') {
12
+ const str = value.trim().toLowerCase();
13
+ if (['1', 'true', 'on', 'yes'].includes(str))
14
+ value = true;
15
+ else if (['0', 'false', 'off', 'no'].includes(str))
16
+ value = false;
17
+ }
18
+ else if (typeof value === 'number') {
19
+ if (value === 1)
20
+ value = true;
21
+ else if (value === 0)
22
+ value = false;
23
+ }
24
+ if (typeof value !== 'boolean')
25
+ return failed(`${field} must be true or false`);
26
+ return success(value);
27
+ }
28
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * 创建 validator 的快捷方式
3
+ */
4
+ import { ArrayValidator, type ArrayOptions } from './array.js';
5
+ import { Validator, type BaseOptions } from './base.js';
6
+ import { BooleanValidator } from './boolean.js';
7
+ import { NumberValidator } from './number.js';
8
+ import { ObjectValidator, type RecordOptions } from './object.js';
9
+ import { StringValidator } from './string.js';
10
+ /** 仅进行基本检查(如检查空值),不检查具体格式 */
11
+ export declare function any(options?: Partial<BaseOptions>): Validator<unknown>;
12
+ export declare function string(...args: ConstructorParameters<typeof StringValidator>): StringValidator;
13
+ export declare function number(...args: ConstructorParameters<typeof NumberValidator>): NumberValidator;
14
+ export declare function boolean(options?: Partial<BaseOptions>): BooleanValidator;
15
+ export declare function array(options: Validator | (ArrayOptions & Partial<BaseOptions>)): ArrayValidator;
16
+ export declare function tuple(validators: Validator[], baseOptions?: Partial<BaseOptions>): ArrayValidator;
17
+ export declare function struct(validators: Record<string, Validator>, options?: Partial<BaseOptions>): ObjectValidator;
18
+ export declare function record(options: Validator | (RecordOptions & Partial<BaseOptions>)): ObjectValidator;