@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.
- package/LICENSE.md +7 -0
- package/README.md +9 -0
- package/dist/compiler.js +57 -0
- package/dist/compiler.js.map +1 -0
- package/dist/formats.js +127 -0
- package/dist/formats.js.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas/native-enum.js +14 -0
- package/dist/schemas/native-enum.js.map +1 -0
- package/dist/schemas/nullable.js +5 -0
- package/dist/schemas/nullable.js.map +1 -0
- package/dist/schemas/union-enum.js +13 -0
- package/dist/schemas/union-enum.js.map +1 -0
- package/dist/temporal.js +15 -0
- package/dist/temporal.js.map +1 -0
- package/dist/types/any.js +16 -0
- package/dist/types/any.js.map +1 -0
- package/dist/types/array.js +42 -0
- package/dist/types/array.js.map +1 -0
- package/dist/types/base.js +77 -0
- package/dist/types/base.js.map +1 -0
- package/dist/types/boolean.js +16 -0
- package/dist/types/boolean.js.map +1 -0
- package/dist/types/custom.js +23 -0
- package/dist/types/custom.js.map +1 -0
- package/dist/types/datetime.js +34 -0
- package/dist/types/datetime.js.map +1 -0
- package/dist/types/enum.js +41 -0
- package/dist/types/enum.js.map +1 -0
- package/dist/types/literal.js +21 -0
- package/dist/types/literal.js.map +1 -0
- package/dist/types/never.js +16 -0
- package/dist/types/never.js.map +1 -0
- package/dist/types/number.js +94 -0
- package/dist/types/number.js.map +1 -0
- package/dist/types/object.js +49 -0
- package/dist/types/object.js.map +1 -0
- package/dist/types/string.js +55 -0
- package/dist/types/string.js.map +1 -0
- package/dist/types/temporal.js +144 -0
- package/dist/types/temporal.js.map +1 -0
- package/dist/types/union.js +40 -0
- package/dist/types/union.js.map +1 -0
- package/dist/utils.js +1 -0
- package/dist/utils.js.map +1 -0
- package/package.json +43 -0
- package/src/compiler.ts +68 -0
- package/src/formats.ts +182 -0
- package/src/index.ts +79 -0
- package/src/schemas/native-enum.ts +29 -0
- package/src/schemas/nullable.ts +13 -0
- package/src/schemas/union-enum.ts +43 -0
- package/src/temporal.ts +34 -0
- package/src/types/any.ts +27 -0
- package/src/types/array.ts +66 -0
- package/src/types/base.ts +158 -0
- package/src/types/boolean.ts +27 -0
- package/src/types/custom.ts +36 -0
- package/src/types/datetime.ts +60 -0
- package/src/types/enum.ts +62 -0
- package/src/types/literal.ts +31 -0
- package/src/types/never.ts +30 -0
- package/src/types/number.ts +124 -0
- package/src/types/object.ts +83 -0
- package/src/types/string.ts +87 -0
- package/src/types/temporal.ts +227 -0
- package/src/types/union.ts +79 -0
- package/src/utils.ts +16 -0
- package/tsconfig.json +3 -0
package/src/compiler.ts
ADDED
|
@@ -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
|
+
}
|
package/src/temporal.ts
ADDED
|
@@ -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
|
+
}
|
package/src/types/any.ts
ADDED
|
@@ -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
|
+
}
|