@hy_ong/zod-kit 0.0.2 → 0.0.5

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 (51) hide show
  1. package/.claude/settings.local.json +23 -0
  2. package/LICENSE +21 -0
  3. package/README.md +7 -7
  4. package/debug.js +21 -0
  5. package/debug.ts +16 -0
  6. package/dist/index.cjs +1663 -189
  7. package/dist/index.d.cts +324 -32
  8. package/dist/index.d.ts +324 -32
  9. package/dist/index.js +1634 -187
  10. package/eslint.config.mts +8 -0
  11. package/package.json +10 -9
  12. package/src/config.ts +1 -1
  13. package/src/i18n/locales/en.json +123 -49
  14. package/src/i18n/locales/zh-TW.json +123 -46
  15. package/src/index.ts +13 -7
  16. package/src/validators/common/boolean.ts +97 -0
  17. package/src/validators/common/date.ts +171 -0
  18. package/src/validators/common/email.ts +200 -0
  19. package/src/validators/common/id.ts +259 -0
  20. package/src/validators/common/number.ts +194 -0
  21. package/src/validators/common/password.ts +214 -0
  22. package/src/validators/common/text.ts +151 -0
  23. package/src/validators/common/url.ts +207 -0
  24. package/src/validators/taiwan/business-id.ts +140 -0
  25. package/src/validators/taiwan/fax.ts +182 -0
  26. package/src/validators/taiwan/mobile.ts +110 -0
  27. package/src/validators/taiwan/national-id.ts +208 -0
  28. package/src/validators/taiwan/tel.ts +182 -0
  29. package/tests/common/boolean.test.ts +340 -92
  30. package/tests/common/date.test.ts +458 -0
  31. package/tests/common/email.test.ts +232 -60
  32. package/tests/common/id.test.ts +535 -0
  33. package/tests/common/number.test.ts +230 -60
  34. package/tests/common/password.test.ts +281 -54
  35. package/tests/common/text.test.ts +227 -30
  36. package/tests/common/url.test.ts +492 -67
  37. package/tests/taiwan/business-id.test.ts +240 -0
  38. package/tests/taiwan/fax.test.ts +463 -0
  39. package/tests/taiwan/mobile.test.ts +373 -0
  40. package/tests/taiwan/national-id.test.ts +435 -0
  41. package/tests/taiwan/tel.test.ts +467 -0
  42. package/eslint.config.mjs +0 -10
  43. package/src/common/boolean.ts +0 -37
  44. package/src/common/date.ts +0 -44
  45. package/src/common/email.ts +0 -45
  46. package/src/common/integer.ts +0 -47
  47. package/src/common/number.ts +0 -38
  48. package/src/common/password.ts +0 -34
  49. package/src/common/text.ts +0 -35
  50. package/src/common/url.ts +0 -38
  51. package/tests/common/integer.test.ts +0 -90
@@ -1,38 +0,0 @@
1
- import { z, ZodNullable, ZodNumber } from "zod"
2
- import { t } from "../i18n"
3
-
4
- export type NumberOptions<IsRequired extends boolean = true> = {
5
- required?: IsRequired
6
- label: string
7
- min?: number
8
- max?: number
9
- defaultValue?: IsRequired extends true ? number : number | null
10
- }
11
-
12
- export type NumberSchema<IsRequired extends boolean> = IsRequired extends true ? ZodNumber : ZodNullable<ZodNumber>
13
-
14
- export function number<IsRequired extends boolean = true>(options?: NumberOptions<IsRequired>): NumberSchema<IsRequired> {
15
- const { required = true, label, min, max, defaultValue } = options ?? {}
16
-
17
- const schema = z
18
- .preprocess(
19
- (val) => {
20
- if (val === "" || val === undefined || val === null) return defaultValue ?? null
21
- return typeof val === "string" ? Number(val) : val
22
- },
23
- z.union([
24
- z.number({
25
- error: (issue) => {
26
- if (issue.code === "invalid_type") return t("common.number.integer", { label })
27
- return t("common.number.required", { label })
28
- },
29
- }),
30
- z.null(),
31
- ])
32
- )
33
- .refine((val) => !required || val !== null, { message: t("common.number.required", { label }) })
34
- .refine((val) => val === null || min === undefined || val >= min, { message: t("common.number.min", { label, min }) })
35
- .refine((val) => val === null || max === undefined || val <= max, { message: t("common.number.max", { label, max }) })
36
-
37
- return schema as unknown as NumberSchema<IsRequired>
38
- }
@@ -1,34 +0,0 @@
1
- import { z, ZodNullable, ZodString } from "zod"
2
- import { t } from "../i18n"
3
-
4
- export type PasswordOptions<IsRequired extends boolean = true> = {
5
- required?: IsRequired
6
- label: string
7
- min?: number
8
- max?: number
9
- uppercase?: boolean
10
- lowercase?: boolean
11
- digits?: boolean
12
- special?: boolean
13
- }
14
-
15
- export type PasswordSchema<IsRequired extends boolean> = IsRequired extends true ? ZodString : ZodNullable<ZodString>
16
-
17
- export function password<IsRequired extends boolean = true>(options?: PasswordOptions<IsRequired>): PasswordSchema<IsRequired> {
18
- const { required = true, label, min, max, uppercase, lowercase, digits, special } = options ?? {}
19
-
20
- const baseSchema = required
21
- ? z.preprocess((val) => (val === "" || val === null || val === undefined ? null : val), z.coerce.string().trim())
22
- : z.preprocess((val) => (val === "" || val === null || val === undefined ? null : val), z.coerce.string().trim().nullable())
23
-
24
- const schema = baseSchema
25
- .refine((val) => (required ? val !== "" && val !== "null" && val !== "undefined" : true), { message: t("common.password.required", { label }) })
26
- .refine((val) => val === null || min === undefined || val.length >= min, { message: t("common.password.min", { label, min }) })
27
- .refine((val) => val === null || max === undefined || val.length <= max, { message: t("common.password.max", { label, max }) })
28
- .refine((val) => val === null || !uppercase || /[A-Z]/.test(val), { message: t("common.password.uppercase", { label }) })
29
- .refine((val) => val === null || !lowercase || /[a-z]/.test(val), { message: t("common.password.lowercase", { label }) })
30
- .refine((val) => val === null || !special || /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]+/.test(val), { message: t("common.password.special", { label }) })
31
- .refine((val) => val === null || !digits || /[0-9]/.test(val), { message: t("common.password.digits", { label }) })
32
-
33
- return schema as unknown as PasswordSchema<IsRequired>
34
- }
@@ -1,35 +0,0 @@
1
- import { z, ZodNullable, ZodString } from "zod"
2
- import { t } from "../i18n"
3
-
4
- export type TextOptions<IsRequired extends boolean = true> = {
5
- required?: IsRequired
6
- label: string
7
- min?: number
8
- max?: number
9
- startsWith?: string
10
- endsWith?: string
11
- includes?: string
12
- regex?: RegExp
13
- defaultValue?: IsRequired extends true ? string : string | null
14
- }
15
-
16
- export type TextSchema<IsRequired extends boolean> = IsRequired extends true ? ZodString : ZodNullable<ZodString>
17
-
18
- export function text<IsRequired extends boolean = true>(options?: TextOptions<IsRequired>): TextSchema<IsRequired> {
19
- const { required = true, label, min, max, startsWith, endsWith, includes, regex, defaultValue = null } = options ?? {}
20
-
21
- const baseSchema = required
22
- ? z.preprocess((val) => (val === "" || val === null || val === undefined ? defaultValue : val), z.coerce.string().trim())
23
- : z.preprocess((val) => (val === "" || val === null || val === undefined ? defaultValue : val), z.coerce.string().trim().nullable())
24
-
25
- const schema = baseSchema
26
- .refine((val) => (required ? val !== "" && val !== "null" && val !== "undefined" : true), { message: t("common.text.required", { label }) })
27
- .refine((val) => val === null || min === undefined || val.length >= min, { message: t("common.text.min", { label, min }) })
28
- .refine((val) => val === null || max === undefined || val.length <= max, { message: t("common.text.max", { label, max }) })
29
- .refine((val) => val === null || startsWith === undefined || val.startsWith(startsWith), { message: t("common.text.startsWith", { label, startsWith }) })
30
- .refine((val) => val === null || endsWith === undefined || val.endsWith(endsWith), { message: t("common.text.endsWith", { label, endsWith }) })
31
- .refine((val) => val === null || includes === undefined || val.includes(includes), { message: t("common.text.includes", { label, includes }) })
32
- .refine((val) => val === null || regex === undefined || regex.test(val), { message: t("common.text.invalid", { label, regex }) })
33
-
34
- return schema as unknown as TextSchema<IsRequired>
35
- }
package/src/common/url.ts DELETED
@@ -1,38 +0,0 @@
1
- import { z, ZodNullable, ZodString } from "zod"
2
- import { t } from "../i18n"
3
- import { TextSchema } from "./text"
4
-
5
- export type UrlOptions<IsRequired extends boolean = true> = {
6
- required?: IsRequired
7
- label: string
8
- min?: number
9
- max?: number
10
- includes?: string
11
- }
12
-
13
- export type UrlSchema<IsRequired extends boolean> = IsRequired extends true ? ZodString : ZodNullable<ZodString>
14
-
15
- export function url<IsRequired extends boolean = true>(options?: UrlOptions<IsRequired>): UrlSchema<IsRequired> {
16
- const { required = true, label, min, max, includes } = options ?? {}
17
-
18
- const baseSchema = required
19
- ? z.preprocess(
20
- (val) => (val === "" || val === null || val === undefined ? null : val),
21
- z.url({
22
- error: (issue) => {
23
- if (issue.code === "invalid_type") return t("common.url.required", { label })
24
- else if (issue.code === "invalid_format") return t("common.url.invalid", { label })
25
- return t("common.url.invalid", { label })
26
- },
27
- })
28
- )
29
- : z.preprocess((val) => (val === "" || val === null || val === undefined ? null : val), z.url({ message: t("common.url.invalid", { label }) }).nullable())
30
-
31
- const schema = baseSchema
32
- .refine((val) => (required ? val !== "" && val !== "null" && val !== "undefined" : true), { message: t("common.text.required", { label }) })
33
- .refine((val) => val === null || min === undefined || val.length >= min, { message: t("common.text.min", { label, min }) })
34
- .refine((val) => val === null || max === undefined || val.length <= max, { message: t("common.text.max", { label, max }) })
35
- .refine((val) => val === null || includes === undefined || val.includes(includes), { message: t("common.text.includes", { label, includes }) })
36
-
37
- return schema as unknown as TextSchema<IsRequired>
38
- }
@@ -1,90 +0,0 @@
1
- import { describe, it, expect, beforeEach } from "vitest"
2
- import { setLocale, integer, Locale } from "../../src"
3
-
4
- const locales = [
5
- {
6
- locale: "en",
7
- messages: {
8
- required: "TEST is required",
9
- min: "TEST must be at least 3",
10
- max: "TEST must be at most 10",
11
- integer: "TEST must be an integer",
12
- },
13
- },
14
- {
15
- locale: "zh-TW",
16
- messages: {
17
- required: "請輸入 TEST",
18
- min: "TEST 最小值 3",
19
- max: "TEST 最大值 10",
20
- integer: "TEST 必須為整數",
21
- },
22
- },
23
- ] as const
24
-
25
- describe.each(locales)("integer() locale: $locale", ({ locale, messages }) => {
26
- beforeEach(() => setLocale(locale as Locale))
27
-
28
- it("should pass with valid integer", () => {
29
- const schema = integer({ label: "TEST" })
30
- expect(schema.parse(42)).toBe(42)
31
- })
32
-
33
- it("should parse string number as integer", () => {
34
- const schema = integer({ label: "TEST" })
35
- expect(schema.parse("100")).toBe(100)
36
- })
37
-
38
- it("should fail for decimal value", () => {
39
- const schema = integer({ label: "TEST" })
40
- expect(() => schema.parse(3.14)).toThrow(messages.integer)
41
- })
42
-
43
- it("should fail for non-numeric string", () => {
44
- const schema = integer({ label: "TEST" })
45
- expect(() => schema.parse("abc")).toThrow(messages.integer)
46
- expect(() => schema.parse("10a")).toThrow(messages.integer)
47
- })
48
-
49
- it("should allow null if not required", () => {
50
- const schema = integer({ label: "TEST", required: false })
51
- expect(schema.parse("")).toBe(null)
52
- expect(schema.parse(null)).toBe(null)
53
- expect(schema.parse(undefined)).toBe(null)
54
- })
55
-
56
- it("should fail when required and input is empty", () => {
57
- const schema = integer({ label: "TEST", required: true })
58
- expect(() => schema.parse("")).toThrow(messages.required)
59
- expect(() => schema.parse(null)).toThrow(messages.required)
60
- expect(() => schema.parse(undefined)).toThrow(messages.required)
61
- })
62
-
63
- it("should apply default value even when required", () => {
64
- const schema = integer({ label: "TEST", required: true, defaultValue: 5 })
65
- expect(schema.parse("")).toBe(5)
66
- expect(schema.parse(null)).toBe(5)
67
- expect(schema.parse(undefined)).toBe(5)
68
- })
69
-
70
- it("should apply default value even when required", () => {
71
- const schema = integer({ label: "TEST", required: false, defaultValue: 5 })
72
- expect(schema.parse("")).toBe(5)
73
- expect(schema.parse(null)).toBe(5)
74
- expect(schema.parse(undefined)).toBe(5)
75
- })
76
-
77
- it("should enforce min", () => {
78
- const schema = integer({ label: "TEST", min: 3 })
79
- expect(schema.parse(3)).toBe(3)
80
- expect(() => schema.parse(2)).toThrow(messages.min)
81
- expect(() => schema.parse("2")).toThrow(messages.min)
82
- })
83
-
84
- it("should enforce max", () => {
85
- const schema = integer({ label: "TEST", max: 10 })
86
- expect(schema.parse(10)).toBe(10)
87
- expect(() => schema.parse(11)).toThrow(messages.max)
88
- expect(() => schema.parse("11")).toThrow(messages.max)
89
- })
90
- })