@nmtjs/type 0.1.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 (70) hide show
  1. package/LICENSE.md +7 -0
  2. package/README.md +9 -0
  3. package/dist/compiler.js +57 -0
  4. package/dist/compiler.js.map +1 -0
  5. package/dist/formats.js +127 -0
  6. package/dist/formats.js.map +1 -0
  7. package/dist/index.js +38 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/schemas/native-enum.js +14 -0
  10. package/dist/schemas/native-enum.js.map +1 -0
  11. package/dist/schemas/nullable.js +5 -0
  12. package/dist/schemas/nullable.js.map +1 -0
  13. package/dist/schemas/union-enum.js +13 -0
  14. package/dist/schemas/union-enum.js.map +1 -0
  15. package/dist/temporal.js +15 -0
  16. package/dist/temporal.js.map +1 -0
  17. package/dist/types/any.js +16 -0
  18. package/dist/types/any.js.map +1 -0
  19. package/dist/types/array.js +42 -0
  20. package/dist/types/array.js.map +1 -0
  21. package/dist/types/base.js +77 -0
  22. package/dist/types/base.js.map +1 -0
  23. package/dist/types/boolean.js +16 -0
  24. package/dist/types/boolean.js.map +1 -0
  25. package/dist/types/custom.js +23 -0
  26. package/dist/types/custom.js.map +1 -0
  27. package/dist/types/datetime.js +34 -0
  28. package/dist/types/datetime.js.map +1 -0
  29. package/dist/types/enum.js +41 -0
  30. package/dist/types/enum.js.map +1 -0
  31. package/dist/types/literal.js +21 -0
  32. package/dist/types/literal.js.map +1 -0
  33. package/dist/types/never.js +16 -0
  34. package/dist/types/never.js.map +1 -0
  35. package/dist/types/number.js +94 -0
  36. package/dist/types/number.js.map +1 -0
  37. package/dist/types/object.js +49 -0
  38. package/dist/types/object.js.map +1 -0
  39. package/dist/types/string.js +55 -0
  40. package/dist/types/string.js.map +1 -0
  41. package/dist/types/temporal.js +144 -0
  42. package/dist/types/temporal.js.map +1 -0
  43. package/dist/types/union.js +40 -0
  44. package/dist/types/union.js.map +1 -0
  45. package/dist/utils.js +1 -0
  46. package/dist/utils.js.map +1 -0
  47. package/package.json +43 -0
  48. package/src/compiler.ts +68 -0
  49. package/src/formats.ts +182 -0
  50. package/src/index.ts +79 -0
  51. package/src/schemas/native-enum.ts +29 -0
  52. package/src/schemas/nullable.ts +13 -0
  53. package/src/schemas/union-enum.ts +43 -0
  54. package/src/temporal.ts +34 -0
  55. package/src/types/any.ts +27 -0
  56. package/src/types/array.ts +66 -0
  57. package/src/types/base.ts +158 -0
  58. package/src/types/boolean.ts +27 -0
  59. package/src/types/custom.ts +36 -0
  60. package/src/types/datetime.ts +60 -0
  61. package/src/types/enum.ts +62 -0
  62. package/src/types/literal.ts +31 -0
  63. package/src/types/never.ts +30 -0
  64. package/src/types/number.ts +124 -0
  65. package/src/types/object.ts +83 -0
  66. package/src/types/string.ts +87 -0
  67. package/src/types/temporal.ts +227 -0
  68. package/src/types/union.ts +79 -0
  69. package/src/utils.ts +16 -0
  70. package/tsconfig.json +3 -0
@@ -0,0 +1,68 @@
1
+ import {
2
+ TypeCompiler,
3
+ type ValueErrorIterator,
4
+ } from '@sinclair/typebox/compiler'
5
+ import { Value } from '@sinclair/typebox/value'
6
+ import { type BaseType, typeFinalSchema } from './types/base.ts'
7
+
8
+ export type Compiled = {
9
+ check: (val: unknown) => boolean
10
+ errors: (val: unknown) => ValueErrorIterator
11
+ prepare: (val: unknown) => unknown
12
+ convert: (val: unknown) => unknown
13
+ decode: (
14
+ val: unknown,
15
+ ) => { success: true; value: unknown } | { success: false; error: any }
16
+ encode: (
17
+ val: unknown,
18
+ ) => { success: true; value: unknown } | { success: false; error: any }
19
+ }
20
+
21
+ const compileType = (type: BaseType) => {
22
+ const schema = type[typeFinalSchema]
23
+ const compiled = TypeCompiler.Compile(schema)
24
+ const prepare = (value: any) => {
25
+ for (const fn of [Value.Clean, Value.Default]) {
26
+ value = fn(schema, value)
27
+ }
28
+ return value
29
+ }
30
+ const convert = (value: any) => Value.Convert(schema, value)
31
+ return {
32
+ check: compiled.Check.bind(compiled),
33
+ errors: compiled.Errors.bind(compiled),
34
+ decode: compiled.Decode.bind(compiled),
35
+ encode: compiled.Encode.bind(compiled),
36
+ prepare,
37
+ convert,
38
+ }
39
+ }
40
+
41
+ export const compile = (schema: BaseType): Compiled => {
42
+ const compiled = compileType(schema)
43
+
44
+ // TODO: custom error handling/shaping
45
+ return {
46
+ ...compiled,
47
+ decode: (val) => {
48
+ try {
49
+ return {
50
+ success: true as const,
51
+ value: compiled.decode(compiled.prepare(val)),
52
+ }
53
+ } catch (error) {
54
+ return { success: false as const, error }
55
+ }
56
+ },
57
+ encode: (val) => {
58
+ try {
59
+ return {
60
+ success: true as const,
61
+ value: compiled.encode(compiled.prepare(val)),
62
+ }
63
+ } catch (error) {
64
+ return { success: false as const, error }
65
+ }
66
+ },
67
+ }
68
+ }
package/src/formats.ts ADDED
@@ -0,0 +1,182 @@
1
+ // Source: https://gist.github.com/ChuckJonas/74d9cfb6ba46244ef4eaa5818c06987b
2
+ // TODO: review all of this
3
+
4
+ import { FormatRegistry } from '@sinclair/typebox/type'
5
+
6
+ export const register = () => {
7
+ for (const [name, format] of Object.entries(fullFormats)) {
8
+ if (format === true) {
9
+ FormatRegistry.Set(name, () => true)
10
+ continue
11
+ }
12
+
13
+ if (typeof format === 'function') {
14
+ FormatRegistry.Set(name, format)
15
+ continue
16
+ }
17
+
18
+ FormatRegistry.Set(name, (value) => format.test(value))
19
+ }
20
+ }
21
+
22
+ export const fullFormats: Record<
23
+ string,
24
+ RegExp | ((v: any) => boolean) | true
25
+ > = {
26
+ // date: http://tools.ietf.org/html/rfc3339#section-5.6
27
+ date,
28
+ // date-time: http://tools.ietf.org/html/rfc3339#section-5.6
29
+ time: getTime(true),
30
+ 'date-time': getDateTime(true),
31
+ 'iso-time': getTime(),
32
+ 'iso-date-time': getDateTime(),
33
+ // duration: https://tools.ietf.org/html/rfc3339#appendix-A
34
+ duration:
35
+ /^P(?!$)((\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+S)?)?|(\d+W)?)$/,
36
+ uri,
37
+ 'uri-reference':
38
+ /^(?:[a-z][a-z0-9+\-.]*:)?(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'"()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?(?:\?(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i,
39
+ // uri-template: https://tools.ietf.org/html/rfc6570
40
+ 'uri-template':
41
+ // biome-ignore lint/suspicious/noControlCharactersInRegex:
42
+ /^(?:(?:[^\x00-\x20"'<>%\\^`{|}]|%[0-9a-f]{2})|\{[+#./;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?)*\})*$/i,
43
+ // For the source: https://gist.github.com/dperini/729294
44
+ // For test cases: https://mathiasbynens.be/demo/url-regex
45
+ url: /^(?:https?|ftp):\/\/(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u{00a1}-\u{ffff}]+-)*[a-z0-9\u{00a1}-\u{ffff}]+)(?:\.(?:[a-z0-9\u{00a1}-\u{ffff}]+-)*[a-z0-9\u{00a1}-\u{ffff}]+)*(?:\.(?:[a-z\u{00a1}-\u{ffff}]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/iu,
46
+ email:
47
+ /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i,
48
+ hostname:
49
+ /^(?=.{1,253}\.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*\.?$/i,
50
+ // optimized https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9780596802837/ch07s16.html
51
+ ipv4: /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/,
52
+ ipv6: /^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))$/i,
53
+ regex,
54
+ // uuid: http://tools.ietf.org/html/rfc4122
55
+ uuid: /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i,
56
+ // JSON-pointer: https://tools.ietf.org/html/rfc6901
57
+ // uri fragment: https://tools.ietf.org/html/rfc3986#appendix-A
58
+ 'json-pointer': /^(?:\/(?:[^~/]|~0|~1)*)*$/,
59
+ 'json-pointer-uri-fragment':
60
+ /^#(?:\/(?:[a-z0-9_\-.!$&'()*+,;:=@]|%[0-9a-f]{2}|~0|~1)*)*$/i,
61
+ // relative JSON-pointer: http://tools.ietf.org/html/draft-luff-relative-json-pointer-00
62
+ 'relative-json-pointer': /^(?:0|[1-9][0-9]*)(?:#|(?:\/(?:[^~/]|~0|~1)*)*)$/,
63
+ // the following formats are used by the openapi specification: https://spec.openapis.org/oas/v3.0.0#data-types
64
+ // byte: https://github.com/miguelmota/is-base64
65
+ byte,
66
+ // signed 32 bit integer
67
+ int32: validateInt32,
68
+ // signed 64 bit integer
69
+ int64: validateInt64,
70
+ // C-type float
71
+ float: validateNumber,
72
+ // C-type double
73
+ double: validateNumber,
74
+ // hint to the UI to hide input strings
75
+ password: true,
76
+ // unchecked string payload
77
+ binary: true,
78
+ }
79
+
80
+ function isLeapYear(year: number): boolean {
81
+ // https://tools.ietf.org/html/rfc3339#appendix-C
82
+ return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)
83
+ }
84
+
85
+ const DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/
86
+ const DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
87
+
88
+ function date(str: string): boolean {
89
+ // full-date from http://tools.ietf.org/html/rfc3339#section-5.6
90
+ const matches: string[] | null = DATE.exec(str)
91
+ if (!matches) return false
92
+ const year: number = +matches[1]
93
+ const month: number = +matches[2]
94
+ const day: number = +matches[3]
95
+ return (
96
+ month >= 1 &&
97
+ month <= 12 &&
98
+ day >= 1 &&
99
+ day <= (month === 2 && isLeapYear(year) ? 29 : DAYS[month])
100
+ )
101
+ }
102
+
103
+ const TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i
104
+
105
+ function getTime(strictTimeZone?: boolean): (str: string) => boolean {
106
+ return function time(str: string): boolean {
107
+ const matches: string[] | null = TIME.exec(str)
108
+ if (!matches) return false
109
+ const hr: number = +matches[1]
110
+ const min: number = +matches[2]
111
+ const sec: number = +matches[3]
112
+ const tz: string | undefined = matches[4]
113
+ const tzSign: number = matches[5] === '-' ? -1 : 1
114
+ const tzH: number = +(matches[6] || 0)
115
+ const tzM: number = +(matches[7] || 0)
116
+ if (tzH > 23 || tzM > 59 || (strictTimeZone && !tz)) return false
117
+ if (hr <= 23 && min <= 59 && sec < 60) return true
118
+ // leap second
119
+ const utcMin = min - tzM * tzSign
120
+ const utcHr = hr - tzH * tzSign - (utcMin < 0 ? 1 : 0)
121
+ return (
122
+ (utcHr === 23 || utcHr === -1) &&
123
+ (utcMin === 59 || utcMin === -1) &&
124
+ sec < 61
125
+ )
126
+ }
127
+ }
128
+
129
+ const DATE_TIME_SEPARATOR = /t|\s/i
130
+ function getDateTime(strictTimeZone?: boolean): (str: string) => boolean {
131
+ const time = getTime(strictTimeZone)
132
+
133
+ return function date_time(str: string): boolean {
134
+ // http://tools.ietf.org/html/rfc3339#section-5.6
135
+ const dateTime: string[] = str.split(DATE_TIME_SEPARATOR)
136
+ return dateTime.length === 2 && date(dateTime[0]) && time(dateTime[1])
137
+ }
138
+ }
139
+
140
+ const NOT_URI_FRAGMENT = /\/|:/
141
+ const URI =
142
+ /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i
143
+
144
+ function uri(str: string): boolean {
145
+ // http://jmrware.com/articles/2009/uri_regexp/URI_regex.html + optional protocol + required "."
146
+ return NOT_URI_FRAGMENT.test(str) && URI.test(str)
147
+ }
148
+
149
+ const BYTE =
150
+ /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/gm
151
+
152
+ function byte(str: string): boolean {
153
+ BYTE.lastIndex = 0
154
+ return BYTE.test(str)
155
+ }
156
+
157
+ const MIN_INT32 = -(2 ** 31)
158
+ const MAX_INT32 = 2 ** 31 - 1
159
+
160
+ function validateInt32(value: number): boolean {
161
+ return Number.isInteger(value) && value <= MAX_INT32 && value >= MIN_INT32
162
+ }
163
+
164
+ function validateInt64(value: number): boolean {
165
+ // JSON and javascript max Int is 2**53, so any int that passes isInteger is valid for Int64
166
+ return Number.isInteger(value)
167
+ }
168
+
169
+ function validateNumber(): boolean {
170
+ return true
171
+ }
172
+
173
+ const Z_ANCHOR = /[^\\]\\Z/
174
+ function regex(str: string): boolean {
175
+ if (Z_ANCHOR.test(str)) return false
176
+ try {
177
+ new RegExp(str)
178
+ return true
179
+ } catch (e) {
180
+ return false
181
+ }
182
+ }
package/src/index.ts ADDED
@@ -0,0 +1,79 @@
1
+ import type { TLiteralValue } from '@sinclair/typebox'
2
+ import { ArrayType } from './types/array.ts'
3
+ import type { BaseType, staticType } from './types/base.ts'
4
+ import { BooleanType } from './types/boolean.ts'
5
+ import { CustomType } from './types/custom.ts'
6
+ import { DateTimeType, DateType } from './types/datetime.ts'
7
+ import { EnumType, NativeEnumType } from './types/enum.ts'
8
+ import { LiteralType } from './types/literal.ts'
9
+ import { IntegerType, NumberType } from './types/number.ts'
10
+ import { ObjectType } from './types/object.ts'
11
+ import { StringType } from './types/string.ts'
12
+ import { IntersactionType, UnionType } from './types/union.ts'
13
+
14
+ // register ajv formats
15
+ import { register } from './formats.ts'
16
+ import { AnyType } from './types/any.ts'
17
+ import { NeverType } from './types/never.ts'
18
+ register()
19
+
20
+ export * from './schemas/native-enum.ts'
21
+ export * from './schemas/union-enum.ts'
22
+ export * from './schemas/nullable.ts'
23
+ export {
24
+ BaseType,
25
+ getTypeSchema,
26
+ } from './types/base.ts'
27
+ export { type TSchema } from '@sinclair/typebox'
28
+ export {
29
+ ArrayType,
30
+ BooleanType,
31
+ CustomType,
32
+ DateTimeType,
33
+ DateType,
34
+ EnumType,
35
+ LiteralType,
36
+ IntegerType,
37
+ NumberType,
38
+ ObjectType,
39
+ StringType,
40
+ IntersactionType,
41
+ UnionType,
42
+ AnyType,
43
+ NeverType,
44
+ }
45
+
46
+ export namespace t {
47
+ export namespace infer {
48
+ export type decoded<T extends BaseType> = T[staticType]['decoded']
49
+ export type encoded<T extends BaseType> = T[staticType]['encoded']
50
+ }
51
+ export const never = () => new NeverType()
52
+ export const boolean = () => new BooleanType()
53
+ export const string = () => new StringType()
54
+ export const number = () => new NumberType()
55
+ export const integer = () => new IntegerType()
56
+ export const literal = <T extends TLiteralValue>(value: T) =>
57
+ new LiteralType(value)
58
+ export const nativeEnum = <T extends { [K in string]: K }>(enumLike: T) =>
59
+ new NativeEnumType(enumLike)
60
+ export const arrayEnum = <T extends (string | number)[]>(enumLike: [...T]) =>
61
+ new EnumType(enumLike)
62
+ export const date = () => new DateType()
63
+ export const datetime = () => new DateTimeType()
64
+ export const array = <T extends BaseType>(element: T) =>
65
+ new ArrayType(element)
66
+ export const object = <T extends Record<string, BaseType>>(properties: T) =>
67
+ new ObjectType(properties)
68
+ export const any = () => new AnyType()
69
+ export const or = <T extends [BaseType, BaseType, ...BaseType[]]>(
70
+ ...types: T
71
+ ) => new UnionType(types)
72
+ export const and = <T extends [BaseType, BaseType, ...BaseType[]]>(
73
+ ...types: T
74
+ ) => new IntersactionType(types)
75
+ export const custom = <T>(
76
+ decode: (value: any) => T,
77
+ encode: (value: T) => any,
78
+ ) => new CustomType<T>(decode, encode)
79
+ }
@@ -0,0 +1,29 @@
1
+ import {
2
+ Kind,
3
+ type SchemaOptions,
4
+ type TSchema,
5
+ TypeRegistry,
6
+ } from '@sinclair/typebox/type'
7
+
8
+ export const NativeEnumKind = 'NativeEnum'
9
+ export interface TNativeEnum<T extends Record<string, string>> extends TSchema {
10
+ [Kind]: typeof NativeEnumKind
11
+ static: T[keyof T][]
12
+ enum: T[keyof T][]
13
+ }
14
+
15
+ export function NativeEnum<T extends Record<string, string>>(
16
+ value: T,
17
+ options: SchemaOptions = {},
18
+ ) {
19
+ const values = Object.values(value)
20
+
21
+ function NativeEnumCheck(schema: TNativeEnum<T>, value: unknown) {
22
+ return typeof value === 'string' && schema.enum.includes(value as any)
23
+ }
24
+
25
+ if (!TypeRegistry.Has(NativeEnumKind))
26
+ TypeRegistry.Set(NativeEnumKind, NativeEnumCheck)
27
+
28
+ return { ...options, [Kind]: NativeEnumKind, enum: values } as TNativeEnum<T>
29
+ }
@@ -0,0 +1,13 @@
1
+ import {
2
+ type SchemaOptions,
3
+ type TNull,
4
+ type TSchema,
5
+ type TUnion,
6
+ Type,
7
+ } from '@sinclair/typebox/type'
8
+
9
+ export type TNullable<T extends TSchema> = TUnion<[T, TNull]>
10
+ export const Nullable = <T extends TSchema>(
11
+ schema: T,
12
+ options: SchemaOptions = {},
13
+ ) => Type.Union([schema, Type.Null()], options)
@@ -0,0 +1,43 @@
1
+ import {
2
+ Kind,
3
+ type SchemaOptions,
4
+ type TSchema,
5
+ TypeRegistry,
6
+ } from '@sinclair/typebox/type'
7
+
8
+ export const UnionEnumKind = 'UnionEnum'
9
+
10
+ // Ref: https://github.com/sinclairzx81/typebox/blob/master/example/prototypes/union-enum.ts
11
+
12
+ // -------------------------------------------------------------------------------------
13
+ // TUnionEnum
14
+ // -------------------------------------------------------------------------------------
15
+ export interface TUnionEnum<T extends (string | number)[]> extends TSchema {
16
+ [Kind]: typeof UnionEnumKind
17
+ static: T[number]
18
+ enum: T
19
+ }
20
+
21
+ // -------------------------------------------------------------------------------------
22
+ // UnionEnum
23
+ // -------------------------------------------------------------------------------------
24
+ /** `[Experimental]` Creates a Union type with a `enum` schema representation */
25
+ export function UnionEnum<T extends (string | number)[]>(
26
+ values: [...T],
27
+ options: SchemaOptions = {},
28
+ ) {
29
+ function UnionEnumCheck(
30
+ schema: TUnionEnum<(string | number)[]>,
31
+ value: unknown,
32
+ ) {
33
+ return (
34
+ (typeof value === 'string' || typeof value === 'number') &&
35
+ schema.enum.includes(value)
36
+ )
37
+ }
38
+
39
+ if (!TypeRegistry.Has(UnionEnumKind))
40
+ TypeRegistry.Set(UnionEnumKind, UnionEnumCheck)
41
+
42
+ return { ...options, [Kind]: UnionEnumKind, enum: values } as TUnionEnum<T>
43
+ }
@@ -0,0 +1,34 @@
1
+ import type { t } from './index.ts'
2
+ import {
3
+ DurationType,
4
+ PlainDateTimeType,
5
+ PlainDateType,
6
+ PlainMonthDayType,
7
+ PlainTimeType,
8
+ PlainYearMonthType,
9
+ ZonedDateTimeType,
10
+ } from './types/temporal.ts'
11
+
12
+ export function extend<T extends typeof t>(value: T) {
13
+ return Object.assign({}, value, {
14
+ temporal: {
15
+ plainDate: () => new PlainDateType(),
16
+ plainDatetime: () => new PlainDateTimeType(),
17
+ plainTime: () => new PlainTimeType(),
18
+ zonedDatetime: () => new ZonedDateTimeType(),
19
+ duration: () => new DurationType(),
20
+ plainYearMonth: () => new PlainYearMonthType(),
21
+ plainMonthDay: () => new PlainMonthDayType(),
22
+ },
23
+ })
24
+ }
25
+
26
+ export {
27
+ DurationType,
28
+ PlainDateTimeType,
29
+ PlainDateType,
30
+ PlainMonthDayType,
31
+ PlainTimeType,
32
+ PlainYearMonthType,
33
+ ZonedDateTimeType,
34
+ }
@@ -0,0 +1,27 @@
1
+ import { type TAny, Type } from '@sinclair/typebox'
2
+ import { BaseType } from './base.ts'
3
+
4
+ export class AnyType<
5
+ N extends boolean = false,
6
+ O extends boolean = false,
7
+ > extends BaseType<TAny, N, O> {
8
+ constructor(
9
+ schema = Type.Any(),
10
+ nullable: N = false as N,
11
+ optional: O = false as O,
12
+ ) {
13
+ super(schema, nullable, optional)
14
+ }
15
+
16
+ nullable() {
17
+ return new AnyType(...this._nullable())
18
+ }
19
+
20
+ optional() {
21
+ return new AnyType(...this._optional())
22
+ }
23
+
24
+ nullish() {
25
+ return new AnyType(...this._nullish())
26
+ }
27
+ }
@@ -0,0 +1,66 @@
1
+ import { type ArrayOptions, type TArray, Type } from '@sinclair/typebox'
2
+ import { BaseType, typeFinalSchema } from './base.ts'
3
+
4
+ export class ArrayType<
5
+ T extends BaseType = BaseType,
6
+ N extends boolean = false,
7
+ O extends boolean = false,
8
+ > extends BaseType<TArray<T[typeFinalSchema]>, N, O> {
9
+ constructor(
10
+ readonly element: T,
11
+ readonly options: ArrayOptions = {},
12
+ nullable: N = false as N,
13
+ optional: O = false as O,
14
+ ) {
15
+ super(Type.Array(element[typeFinalSchema]), nullable, optional)
16
+ }
17
+
18
+ nullable() {
19
+ const [_, ...args] = this._nullable()
20
+ return new ArrayType(this.element, this.options, ...args)
21
+ }
22
+
23
+ optional() {
24
+ const [_, ...args] = this._optional()
25
+ return new ArrayType(this.element, this.options, ...args)
26
+ }
27
+
28
+ nullish() {
29
+ const [_, ...args] = this._nullish()
30
+ return new ArrayType(this.element, this.options, ...args)
31
+ }
32
+
33
+ min(value: number) {
34
+ return new ArrayType(
35
+ this.element,
36
+ {
37
+ ...this.options,
38
+ minItems: value,
39
+ },
40
+ ...this._isNullableOptional,
41
+ )
42
+ }
43
+
44
+ max(value: number) {
45
+ return new ArrayType(
46
+ this.element,
47
+ {
48
+ ...this.options,
49
+ maxItems: value,
50
+ },
51
+ ...this._isNullableOptional,
52
+ )
53
+ }
54
+
55
+ length(value: number) {
56
+ return new ArrayType(
57
+ this.element,
58
+ {
59
+ ...this.options,
60
+ minItems: value,
61
+ maxItems: value,
62
+ },
63
+ ...this._isNullableOptional,
64
+ )
65
+ }
66
+ }