@hy_ong/zod-kit 0.0.4 → 0.0.6

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 (59) hide show
  1. package/.claude/settings.local.json +28 -0
  2. package/LICENSE +21 -0
  3. package/README.md +465 -97
  4. package/debug.js +21 -0
  5. package/debug.ts +16 -0
  6. package/dist/index.cjs +3127 -146
  7. package/dist/index.d.cts +3021 -25
  8. package/dist/index.d.ts +3021 -25
  9. package/dist/index.js +3081 -144
  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 +161 -25
  14. package/src/i18n/locales/zh-TW.json +165 -26
  15. package/src/index.ts +17 -7
  16. package/src/validators/common/boolean.ts +191 -0
  17. package/src/validators/common/date.ts +299 -0
  18. package/src/validators/common/datetime.ts +673 -0
  19. package/src/validators/common/email.ts +313 -0
  20. package/src/validators/common/file.ts +384 -0
  21. package/src/validators/common/id.ts +471 -0
  22. package/src/validators/common/number.ts +319 -0
  23. package/src/validators/common/password.ts +386 -0
  24. package/src/validators/common/text.ts +271 -0
  25. package/src/validators/common/time.ts +600 -0
  26. package/src/validators/common/url.ts +347 -0
  27. package/src/validators/taiwan/business-id.ts +262 -0
  28. package/src/validators/taiwan/fax.ts +327 -0
  29. package/src/validators/taiwan/mobile.ts +242 -0
  30. package/src/validators/taiwan/national-id.ts +425 -0
  31. package/src/validators/taiwan/postal-code.ts +1049 -0
  32. package/src/validators/taiwan/tel.ts +330 -0
  33. package/tests/common/boolean.test.ts +340 -92
  34. package/tests/common/date.test.ts +458 -0
  35. package/tests/common/datetime.test.ts +693 -0
  36. package/tests/common/email.test.ts +232 -60
  37. package/tests/common/file.test.ts +479 -0
  38. package/tests/common/id.test.ts +535 -0
  39. package/tests/common/number.test.ts +230 -60
  40. package/tests/common/password.test.ts +271 -44
  41. package/tests/common/text.test.ts +210 -13
  42. package/tests/common/time.test.ts +528 -0
  43. package/tests/common/url.test.ts +492 -67
  44. package/tests/taiwan/business-id.test.ts +240 -0
  45. package/tests/taiwan/fax.test.ts +463 -0
  46. package/tests/taiwan/mobile.test.ts +373 -0
  47. package/tests/taiwan/national-id.test.ts +435 -0
  48. package/tests/taiwan/postal-code.test.ts +705 -0
  49. package/tests/taiwan/tel.test.ts +467 -0
  50. package/eslint.config.mjs +0 -10
  51. package/src/common/boolean.ts +0 -36
  52. package/src/common/date.ts +0 -43
  53. package/src/common/email.ts +0 -44
  54. package/src/common/integer.ts +0 -46
  55. package/src/common/number.ts +0 -37
  56. package/src/common/password.ts +0 -33
  57. package/src/common/text.ts +0 -34
  58. package/src/common/url.ts +0 -37
  59. package/tests/common/integer.test.ts +0 -90
@@ -0,0 +1,535 @@
1
+ import { describe, expect, it, beforeEach } from "vitest"
2
+ import { Locale, setLocale, id, detectIdType, validateIdType, ID_PATTERNS } from "../../src"
3
+
4
+ const locales = [
5
+ {
6
+ locale: "en",
7
+ messages: {
8
+ required: "Required",
9
+ invalid: "Invalid ID format",
10
+ minLength: "Must be at least 5 characters",
11
+ maxLength: "Must be at most 10 characters",
12
+ numeric: "Must be a numeric ID",
13
+ uuid: "Must be a valid UUID",
14
+ objectId: "Must be a valid MongoDB ObjectId",
15
+ nanoid: "Must be a valid Nano ID",
16
+ snowflake: "Must be a valid Snowflake ID",
17
+ cuid: "Must be a valid CUID",
18
+ ulid: "Must be a valid ULID",
19
+ shortid: "Must be a valid Short ID",
20
+ customFormat: "Invalid ID format",
21
+ includes: "Must include test",
22
+ excludes: "Must not contain bad",
23
+ startsWith: "Must start with id",
24
+ endsWith: "Must end with _end",
25
+ },
26
+ },
27
+ {
28
+ locale: "zh-TW",
29
+ messages: {
30
+ required: "必填",
31
+ invalid: "無效的 ID 格式",
32
+ minLength: "長度至少 5 字元",
33
+ maxLength: "長度最多 10 字元",
34
+ numeric: "必須為數字 ID",
35
+ uuid: "必須為有效的 UUID",
36
+ objectId: "必須為有效的 MongoDB ObjectId",
37
+ nanoid: "必須為有效的 Nano ID",
38
+ snowflake: "必須為有效的 Snowflake ID",
39
+ cuid: "必須為有效的 CUID",
40
+ ulid: "必須為有效的 ULID",
41
+ shortid: "必須為有效的 Short ID",
42
+ customFormat: "無效的 ID 格式",
43
+ includes: "必須包含「test」",
44
+ excludes: "不得包含「bad」",
45
+ startsWith: "必須以「id」開頭",
46
+ endsWith: "必須以「_end」結尾",
47
+ },
48
+ },
49
+ ] as const
50
+
51
+ // Valid test IDs for different formats
52
+ const validIds = {
53
+ numeric: ["1", "123", "999999", "0"],
54
+ uuid: [
55
+ "550e8400-e29b-41d4-a716-446655440000",
56
+ "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
57
+ "f47ac10b-58cc-4372-a567-0e02b2c3d479"
58
+ ],
59
+ objectId: [
60
+ "507f1f77bcf86cd799439011",
61
+ "507f191e810c19729de860ea",
62
+ "5a9427648b0beebeb69579cc"
63
+ ],
64
+ nanoid: [
65
+ "V1StGXR8_Z5jdHi6B-myT",
66
+ "3IBBoOd_b1YSlnKdvQ8fK",
67
+ "9_xnJ2QZt8vKl3_Kj5f7N"
68
+ ],
69
+ snowflake: [
70
+ "1234567890123456789",
71
+ "9876543210987654321",
72
+ "5555555555555555555"
73
+ ],
74
+ cuid: [
75
+ "cjld2cjxh0000qzrmn831i7rn",
76
+ "ckfr9f3rm0000jo082qvo1234",
77
+ "cl9ebqhxs000109kygp36rup2"
78
+ ],
79
+ ulid: [
80
+ "01BX5ZZKBKACTAV9WEVGEMMVRY",
81
+ "01F4A9WRNHXQM6WR0P9T2XKZG4",
82
+ "01GFQJ8KWT7M9QZRV4S3N2P8X5"
83
+ ],
84
+ shortid: [
85
+ "S1x8U-213",
86
+ "B1Ox_-hV2",
87
+ "rkH-OgHfr",
88
+ "H1f_-OxBf",
89
+ "_rJfOgSGr"
90
+ ]
91
+ } as const
92
+
93
+ // Invalid test IDs for different formats
94
+ const invalidIds = {
95
+ numeric: ["abc", "12a", ""],
96
+ uuid: [
97
+ "550e8400-e29b-41d4-a716-44665544000", // too short
98
+ "550e8400-e29b-41d4-a716-44665544000g", // too long
99
+ "550e8400-e29b-61d4-a716-446655440000", // wrong version (6 instead of 1-5)
100
+ "550e8400-e29b-41d4-c716-446655440000", // wrong variant (c instead of 8,9,a,b)
101
+ "not-a-uuid-at-all"
102
+ ],
103
+ objectId: [
104
+ "507f1f77bcf86cd79943901", // too short
105
+ "507f1f77bcf86cd799439011g", // invalid character
106
+ "507f1f77bcf86cd799439011aa" // too long
107
+ ],
108
+ nanoid: [
109
+ "V1StGXR8_Z5jdHi6B-myT_", // too long
110
+ "V1StGXR8_Z5jdHi6B-my", // too short
111
+ "V1StGXR8_Z5jdHi6B-my&" // invalid character
112
+ ],
113
+ snowflake: [
114
+ "123456789012345678", // too short
115
+ "12345678901234567890", // too long
116
+ "123456789012345678a" // invalid character
117
+ ],
118
+ cuid: [
119
+ "jld2cjxh0000qzrmn831i7rn", // missing 'c' prefix
120
+ "cjld2cjxh0000qzrmn831i7rn_", // too long
121
+ "cjld2cjxh0000qzrmn831i7r" // too short
122
+ ],
123
+ ulid: [
124
+ "01BX5ZZKBKACTAV9WEVGEMMVR", // too short
125
+ "01BX5ZZKBKACTAV9WEVGEMMVRYX", // too long
126
+ "01BX5ZZKBKACTAV9WEVGEMMVO" // invalid character 'O'
127
+ ],
128
+ shortid: [
129
+ "S1x8U-", // too short
130
+ "S1x8U-213-very-long", // too long
131
+ "S1x8U-213+" // invalid character
132
+ ]
133
+ } as const
134
+
135
+ describe.each(locales)("id() locale: $locale", ({ locale, messages }) => {
136
+ beforeEach(() => setLocale(locale as Locale))
137
+
138
+ describe("basic functionality", () => {
139
+ it("should pass with valid ID", () => {
140
+ const schema = id()
141
+ expect(schema.parse("123")).toBe("123")
142
+ expect(schema.parse("550e8400-e29b-41d4-a716-446655440000")).toBe("550e8400-e29b-41d4-a716-446655440000")
143
+ })
144
+
145
+ it("should fail with empty string when required", () => {
146
+ const schema = id()
147
+ expect(() => schema.parse("")).toThrow(messages.required)
148
+ expect(() => schema.parse(null)).toThrow(messages.required)
149
+ expect(() => schema.parse(undefined)).toThrow(messages.required)
150
+ })
151
+
152
+ it("should pass with null when not required", () => {
153
+ const schema = id({ required: false })
154
+ expect(schema.parse("")).toBe(null)
155
+ expect(schema.parse(null)).toBe(null)
156
+ expect(schema.parse(undefined)).toBe(null)
157
+ })
158
+
159
+ it("should handle default values", () => {
160
+ const schema = id({ required: false, defaultValue: "123" })
161
+ expect(schema.parse("")).toBe("123")
162
+ expect(schema.parse(null)).toBe("123")
163
+ expect(schema.parse(undefined)).toBe("123")
164
+ expect(schema.parse("456")).toBe("456")
165
+ })
166
+
167
+ it("should apply transform function", () => {
168
+ const schema = id({ type: "numeric", transform: (val) => val.toUpperCase() })
169
+ expect(schema.parse("123")).toBe("123")
170
+ })
171
+ })
172
+
173
+ describe("length validation", () => {
174
+ it("should fail with string shorter than minLength", () => {
175
+ const schema = id({ minLength: 5 })
176
+ expect(() => schema.parse("123")).toThrow(messages.minLength)
177
+ })
178
+
179
+ it("should fail with string longer than maxLength", () => {
180
+ const schema = id({ maxLength: 10 })
181
+ expect(() => schema.parse("12345678901")).toThrow(messages.maxLength)
182
+ })
183
+
184
+ it("should pass with valid length", () => {
185
+ const schema = id({ minLength: 3, maxLength: 10 })
186
+ expect(schema.parse("12345")).toBe("12345")
187
+ })
188
+ })
189
+
190
+ describe("ID type validation", () => {
191
+ describe("numeric IDs", () => {
192
+ it("should accept valid numeric IDs", () => {
193
+ const schema = id({ type: "numeric" })
194
+ validIds.numeric.forEach(validId => {
195
+ expect(schema.parse(validId)).toBe(validId)
196
+ })
197
+ })
198
+
199
+ it("should reject invalid numeric IDs", () => {
200
+ const schema = id({ type: "numeric" })
201
+ invalidIds.numeric.forEach(invalidId => {
202
+ expect(() => schema.parse(invalidId)).toThrow()
203
+ })
204
+ })
205
+ })
206
+
207
+ describe("UUID validation", () => {
208
+ it("should accept valid UUIDs", () => {
209
+ const schema = id({ type: "uuid" })
210
+ validIds.uuid.forEach(validUuid => {
211
+ expect(schema.parse(validUuid)).toBe(validUuid)
212
+ })
213
+ })
214
+
215
+ it("should reject invalid UUIDs", () => {
216
+ const schema = id({ type: "uuid" })
217
+ invalidIds.uuid.forEach(invalidUuid => {
218
+ expect(() => schema.parse(invalidUuid)).toThrow(messages.uuid)
219
+ })
220
+ })
221
+ })
222
+
223
+ describe("ObjectId validation", () => {
224
+ it("should accept valid ObjectIds", () => {
225
+ const schema = id({ type: "objectId" })
226
+ validIds.objectId.forEach(validObjectId => {
227
+ expect(schema.parse(validObjectId)).toBe(validObjectId)
228
+ })
229
+ })
230
+
231
+ it("should reject invalid ObjectIds", () => {
232
+ const schema = id({ type: "objectId" })
233
+ invalidIds.objectId.forEach(invalidObjectId => {
234
+ expect(() => schema.parse(invalidObjectId)).toThrow(messages.objectId)
235
+ })
236
+ })
237
+ })
238
+
239
+ describe("Nanoid validation", () => {
240
+ it("should accept valid Nanoids", () => {
241
+ const schema = id({ type: "nanoid" })
242
+ validIds.nanoid.forEach(validNanoid => {
243
+ expect(schema.parse(validNanoid)).toBe(validNanoid)
244
+ })
245
+ })
246
+
247
+ it("should reject invalid Nanoids", () => {
248
+ const schema = id({ type: "nanoid" })
249
+ invalidIds.nanoid.forEach(invalidNanoid => {
250
+ expect(() => schema.parse(invalidNanoid)).toThrow(messages.nanoid)
251
+ })
252
+ })
253
+ })
254
+
255
+ describe("Snowflake validation", () => {
256
+ it("should accept valid Snowflake IDs", () => {
257
+ const schema = id({ type: "snowflake" })
258
+ validIds.snowflake.forEach(validSnowflake => {
259
+ expect(schema.parse(validSnowflake)).toBe(validSnowflake)
260
+ })
261
+ })
262
+
263
+ it("should reject invalid Snowflake IDs", () => {
264
+ const schema = id({ type: "snowflake" })
265
+ invalidIds.snowflake.forEach(invalidSnowflake => {
266
+ expect(() => schema.parse(invalidSnowflake)).toThrow(messages.snowflake)
267
+ })
268
+ })
269
+ })
270
+
271
+ describe("CUID validation", () => {
272
+ it("should accept valid CUIDs", () => {
273
+ const schema = id({ type: "cuid" })
274
+ validIds.cuid.forEach(validCuid => {
275
+ expect(schema.parse(validCuid)).toBe(validCuid)
276
+ })
277
+ })
278
+
279
+ it("should reject invalid CUIDs", () => {
280
+ const schema = id({ type: "cuid" })
281
+ invalidIds.cuid.forEach(invalidCuid => {
282
+ expect(() => schema.parse(invalidCuid)).toThrow(messages.cuid)
283
+ })
284
+ })
285
+ })
286
+
287
+ describe("ULID validation", () => {
288
+ it("should accept valid ULIDs", () => {
289
+ const schema = id({ type: "ulid" })
290
+ validIds.ulid.forEach(validUlid => {
291
+ expect(schema.parse(validUlid)).toBe(validUlid)
292
+ })
293
+ })
294
+
295
+ it("should reject invalid ULIDs", () => {
296
+ const schema = id({ type: "ulid" })
297
+ invalidIds.ulid.forEach(invalidUlid => {
298
+ expect(() => schema.parse(invalidUlid)).toThrow(messages.ulid)
299
+ })
300
+ })
301
+ })
302
+
303
+ describe("ShortId validation", () => {
304
+ it("should accept valid ShortIds", () => {
305
+ const schema = id({ type: "shortid" })
306
+ validIds.shortid.forEach(validShortid => {
307
+ expect(schema.parse(validShortid)).toBe(validShortid)
308
+ })
309
+ })
310
+
311
+ it("should reject invalid ShortIds", () => {
312
+ const schema = id({ type: "shortid" })
313
+ invalidIds.shortid.forEach(invalidShortid => {
314
+ expect(() => schema.parse(invalidShortid)).toThrow(messages.shortid)
315
+ })
316
+ })
317
+ })
318
+
319
+ describe("auto detection", () => {
320
+ it("should accept any valid ID format when type is auto", () => {
321
+ const schema = id({ type: "auto" })
322
+
323
+ Object.values(validIds).flat().forEach(validId => {
324
+ expect(schema.parse(validId)).toBe(validId)
325
+ })
326
+ })
327
+
328
+ it("should reject invalid ID formats when type is auto", () => {
329
+ const schema = id({ type: "auto" })
330
+
331
+ expect(() => schema.parse("bad-id")).toThrow(messages.invalid)
332
+ expect(() => schema.parse("abc")).toThrow(messages.invalid)
333
+ expect(() => schema.parse("!@#$%")).toThrow(messages.invalid)
334
+ })
335
+ })
336
+
337
+ describe("allowedTypes", () => {
338
+ it("should accept IDs that match any allowed type", () => {
339
+ const schema = id({ allowedTypes: ["numeric", "uuid"] })
340
+
341
+ expect(schema.parse("123")).toBe("123")
342
+ expect(schema.parse("550e8400-e29b-41d4-a716-446655440000")).toBe("550e8400-e29b-41d4-a716-446655440000")
343
+ })
344
+
345
+ it("should reject IDs that don't match allowed types", () => {
346
+ const schema = id({ allowedTypes: ["numeric", "uuid"] })
347
+
348
+ expect(() => schema.parse("507f1f77bcf86cd799439011")).toThrow() // ObjectId
349
+ expect(() => schema.parse("V1StGXR8_Z5jdHi6B-myT")).toThrow() // Nanoid
350
+ })
351
+ })
352
+ })
353
+
354
+ describe("content validation", () => {
355
+ it("should enforce includes requirement", () => {
356
+ const schema = id({ includes: "test" })
357
+ expect(() => schema.parse("123")).toThrow(messages.includes)
358
+ expect(schema.parse("test123")).toBe("test123")
359
+ })
360
+
361
+ it("should enforce excludes requirement with string", () => {
362
+ const schema = id({ excludes: "bad" })
363
+ expect(() => schema.parse("bad123")).toThrow(messages.excludes)
364
+ expect(schema.parse("good123")).toBe("good123")
365
+ })
366
+
367
+ it("should enforce excludes requirement with array", () => {
368
+ const schema = id({ excludes: ["bad", "evil"] })
369
+ expect(() => schema.parse("bad123")).toThrow()
370
+ expect(() => schema.parse("evil123")).toThrow()
371
+ expect(schema.parse("good123")).toBe("good123")
372
+ })
373
+
374
+ it("should enforce startsWith requirement", () => {
375
+ const schema = id({ startsWith: "id" })
376
+ expect(() => schema.parse("123")).toThrow(messages.startsWith)
377
+ expect(schema.parse("id123")).toBe("id123")
378
+ })
379
+
380
+ it("should enforce endsWith requirement", () => {
381
+ const schema = id({ endsWith: "_end" })
382
+ expect(() => schema.parse("123")).toThrow(messages.endsWith)
383
+ expect(schema.parse("123_end")).toBe("123_end")
384
+ })
385
+
386
+ it("should enforce custom regex requirement", () => {
387
+ const schema = id({ customRegex: /^[A-Z]{2}\d{4}$/ })
388
+ expect(() => schema.parse("ab1234")).toThrow(messages.customFormat)
389
+ expect(() => schema.parse("AB123")).toThrow(messages.customFormat)
390
+ expect(schema.parse("AB1234")).toBe("AB1234")
391
+ })
392
+ })
393
+
394
+ describe("case sensitivity", () => {
395
+ it("should be case sensitive by default", () => {
396
+ const schema = id({ startsWith: "ID" })
397
+ expect(() => schema.parse("id123")).toThrow()
398
+ expect(schema.parse("ID123")).toBe("ID123")
399
+ })
400
+
401
+ it("should be case insensitive when configured", () => {
402
+ const schema = id({ startsWith: "id", caseSensitive: false })
403
+ expect(schema.parse("id123")).toBe("id123")
404
+ expect(schema.parse("ID123")).toBe("id123") // transformed to lowercase
405
+ })
406
+
407
+ it("should preserve case for UUID and ObjectId even when case insensitive", () => {
408
+ const schema = id({ type: "uuid", caseSensitive: false })
409
+ const uuid = "550E8400-E29B-41D4-A716-446655440000"
410
+ expect(schema.parse(uuid)).toBe(uuid) // case preserved for UUID
411
+ })
412
+ })
413
+
414
+ describe("custom i18n messages", () => {
415
+ it("should use custom i18n messages when provided", () => {
416
+ const customMessages = {
417
+ en: {
418
+ required: "Custom required message",
419
+ invalid: "Custom invalid message",
420
+ numeric: "Custom numeric message",
421
+ },
422
+ "zh-TW": {
423
+ required: "自訂必填訊息",
424
+ invalid: "自訂無效訊息",
425
+ numeric: "自訂數字訊息",
426
+ },
427
+ }
428
+
429
+ const schema = id({
430
+ type: "numeric",
431
+ i18n: customMessages,
432
+ })
433
+
434
+ if (locale === "en") {
435
+ expect(() => schema.parse("")).toThrow("Custom required message")
436
+ expect(() => schema.parse("abc")).toThrow("Custom numeric message")
437
+ } else {
438
+ expect(() => schema.parse("")).toThrow("自訂必填訊息")
439
+ expect(() => schema.parse("abc")).toThrow("自訂數字訊息")
440
+ }
441
+ })
442
+
443
+ it("should fallback to default i18n when custom message not provided", () => {
444
+ const customMessages = {
445
+ en: { required: "Custom required message" },
446
+ "zh-TW": { required: "自訂必填訊息" },
447
+ }
448
+
449
+ const schema = id({
450
+ type: "numeric",
451
+ i18n: customMessages,
452
+ })
453
+
454
+ if (locale === "en") {
455
+ expect(() => schema.parse("")).toThrow("Custom required message")
456
+ expect(() => schema.parse("abc")).toThrow(messages.numeric)
457
+ } else {
458
+ expect(() => schema.parse("")).toThrow("自訂必填訊息")
459
+ expect(() => schema.parse("abc")).toThrow(messages.numeric)
460
+ }
461
+ })
462
+ })
463
+
464
+ describe("complex scenarios", () => {
465
+ it("should handle all features combined", () => {
466
+ const schema = id({
467
+ type: "uuid",
468
+ minLength: 30,
469
+ maxLength: 50,
470
+ includes: "-",
471
+ excludes: ["test", "bad"],
472
+ startsWith: "550e",
473
+ caseSensitive: false,
474
+ })
475
+
476
+ // Should fail for various reasons
477
+ expect(() => schema.parse("123")).toThrow() // not UUID
478
+ expect(() => schema.parse("550e8400-e29b-41d4-test-446655440000")).toThrow() // contains "test"
479
+ expect(() => schema.parse("abc-123")).toThrow() // doesn't start with "550e"
480
+
481
+ // Should pass (UUID preserves case even with caseSensitive: false)
482
+ expect(schema.parse("550E8400-E29B-41D4-A716-446655440000")).toBe("550E8400-E29B-41D4-A716-446655440000")
483
+ })
484
+
485
+ it("should work with optional ID and all features", () => {
486
+ const schema = id({
487
+ required: false,
488
+ type: "numeric",
489
+ minLength: 3,
490
+ maxLength: 10,
491
+ })
492
+
493
+ expect(schema.parse(null)).toBe(null)
494
+ expect(schema.parse("")).toBe(null)
495
+ expect(() => schema.parse("ab")).toThrow() // not numeric
496
+ expect(() => schema.parse("12")).toThrow() // too short
497
+ expect(schema.parse("12345")).toBe("12345")
498
+ })
499
+
500
+ it("should handle multiple allowed types with constraints", () => {
501
+ const schema = id({
502
+ allowedTypes: ["uuid", "objectId"],
503
+ includes: "4",
504
+ minLength: 20,
505
+ })
506
+
507
+ expect(schema.parse("550e8400-e29b-41d4-a716-446655440000")).toBe("550e8400-e29b-41d4-a716-446655440000")
508
+ expect(schema.parse("507f1f77bcf86cd799439041")).toBe("507f1f77bcf86cd799439041")
509
+ expect(() => schema.parse("123456")).toThrow() // numeric not allowed
510
+ expect(() => schema.parse("507f1f77bcf86cd799339011")).toThrow() // no "4"
511
+ })
512
+ })
513
+
514
+ describe("utility functions", () => {
515
+ it("should expose detectIdType function", () => {
516
+ expect(detectIdType("123")).toBe("numeric")
517
+ expect(detectIdType("550e8400-e29b-41d4-a716-446655440000")).toBe("uuid")
518
+ expect(detectIdType("507f1f77bcf86cd799439011")).toBe("objectId")
519
+ expect(detectIdType("not-id")).toBe(null)
520
+ })
521
+
522
+ it("should expose validateIdType function", () => {
523
+ expect(validateIdType("123", "numeric")).toBe(true)
524
+ expect(validateIdType("abc", "numeric")).toBe(false)
525
+ expect(validateIdType("550e8400-e29b-41d4-a716-446655440000", "uuid")).toBe(true)
526
+ expect(validateIdType("invalid-uuid", "uuid")).toBe(false)
527
+ })
528
+
529
+ it("should expose ID_PATTERNS constant", () => {
530
+ expect(ID_PATTERNS.numeric.test("123")).toBe(true)
531
+ expect(ID_PATTERNS.uuid.test("550e8400-e29b-41d4-a716-446655440000")).toBe(true)
532
+ expect(ID_PATTERNS.objectId.test("507f1f77bcf86cd799439011")).toBe(true)
533
+ })
534
+ })
535
+ })