valiq-js 0.0.0-stage → 0.1.1
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/CHANGELOG.md +28 -0
- package/LICENSE +21 -0
- package/README.md +342 -2
- package/SECURITY.md +28 -0
- package/dist/_virtual/_rolldown/runtime.cjs +12 -0
- package/dist/checks/formats.cjs +234 -0
- package/dist/checks/formats.d.cts +50 -0
- package/dist/checks/formats.d.ts +50 -0
- package/dist/checks/formats.js +235 -0
- package/dist/checks/values.cjs +149 -0
- package/dist/checks/values.d.cts +24 -0
- package/dist/checks/values.d.ts +24 -0
- package/dist/checks/values.js +176 -0
- package/dist/core/context.cjs +127 -0
- package/dist/core/context.d.cts +59 -0
- package/dist/core/context.d.ts +59 -0
- package/dist/core/context.js +126 -0
- package/dist/core/error.cjs +46 -0
- package/dist/core/error.d.cts +20 -0
- package/dist/core/error.d.ts +20 -0
- package/dist/core/error.js +47 -0
- package/dist/core/issues.cjs +84 -0
- package/dist/core/issues.d.cts +115 -0
- package/dist/core/issues.d.ts +115 -0
- package/dist/core/issues.js +111 -0
- package/dist/core/schema.cjs +347 -0
- package/dist/core/schema.d.cts +135 -0
- package/dist/core/schema.d.ts +135 -0
- package/dist/core/schema.js +326 -0
- package/dist/core/standard.d.cts +55 -0
- package/dist/core/standard.d.ts +55 -0
- package/dist/core/standard.js +4 -0
- package/dist/index.cjs +44 -0
- package/dist/index.d.cts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +9 -0
- package/dist/json-schema/compile.cjs +1113 -0
- package/dist/json-schema/compile.d.cts +81 -0
- package/dist/json-schema/compile.d.ts +81 -0
- package/dist/json-schema/compile.js +1188 -0
- package/dist/json-schema/index.cjs +48 -0
- package/dist/json-schema/index.d.cts +33 -0
- package/dist/json-schema/index.d.ts +33 -0
- package/dist/json-schema/index.js +47 -0
- package/dist/json-schema/infer.d.cts +77 -0
- package/dist/json-schema/infer.d.ts +77 -0
- package/dist/json-schema/infer.js +4 -0
- package/dist/schemas/array.cjs +190 -0
- package/dist/schemas/array.d.cts +35 -0
- package/dist/schemas/array.d.ts +35 -0
- package/dist/schemas/array.js +173 -0
- package/dist/schemas/composite.cjs +308 -0
- package/dist/schemas/composite.d.cts +59 -0
- package/dist/schemas/composite.d.ts +59 -0
- package/dist/schemas/composite.js +318 -0
- package/dist/schemas/object.cjs +203 -0
- package/dist/schemas/object.d.cts +64 -0
- package/dist/schemas/object.d.ts +64 -0
- package/dist/schemas/object.js +196 -0
- package/dist/schemas/primitives.cjs +619 -0
- package/dist/schemas/primitives.d.cts +144 -0
- package/dist/schemas/primitives.d.ts +144 -0
- package/dist/schemas/primitives.js +543 -0
- package/dist/to-json-schema.cjs +300 -0
- package/dist/to-json-schema.d.cts +24 -0
- package/dist/to-json-schema.d.ts +24 -0
- package/dist/to-json-schema.js +336 -0
- package/dist/v.cjs +144 -0
- package/dist/v.d.cts +67 -0
- package/dist/v.d.ts +67 -0
- package/dist/v.js +100 -0
- package/package.json +128 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses
|
|
5
|
+
[Semantic Versioning](https://semver.org/). Before 1.0, breaking changes bump the minor version.
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - 2026-10-25
|
|
8
|
+
|
|
9
|
+
First release.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Builder API (`v`): strings with formats and transforms, numbers, booleans, dates, literals,
|
|
14
|
+
enums (including TypeScript enums), objects with strict/strip/catchall modes and shape helpers,
|
|
15
|
+
arrays, tuples with rest items, records, unions with automatic discriminator detection,
|
|
16
|
+
intersections, recursive schemas, `instance`, `custom`, and strict `coerce` conversions.
|
|
17
|
+
- `parse`, `safeParse` and `is` on every schema; `optional`, `nullable`, `nullish`, `default`,
|
|
18
|
+
`refine`, `transform`, `pipe`, `brand` and `meta` wrappers.
|
|
19
|
+
- Structured issues with stable codes, exact paths and constraint details; `ValiqError` with
|
|
20
|
+
`flatten()` and `format()`; per-check messages and a `messages` option for localisation.
|
|
21
|
+
- `valiq-js/json-schema`: `fromJSONSchema` for draft 2020-12, 2019-09 and draft-07 with strict
|
|
22
|
+
keyword checking, 15 formats, custom formats, registered remote schemas and type inference from
|
|
23
|
+
`as const` schemas. Passes every runnable test of the official JSON-Schema-Test-Suite (required
|
|
24
|
+
suites); `unevaluated*`, `$dynamicRef` and `$recursiveRef` are rejected as unsupported.
|
|
25
|
+
- `toJSONSchema` for 2020-12 and draft-07, with input/output modes.
|
|
26
|
+
- Standard Schema v1 and Standard JSON Schema v1 support.
|
|
27
|
+
- Resource limits (`maxDepth`, `maxIssues`), prototype-pollution-safe copying, linear-time
|
|
28
|
+
built-in formats.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mohsin Zia
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,343 @@
|
|
|
1
|
-
#
|
|
1
|
+
# valiq-js
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Modern validation. Precise errors. Type-safe by design.**
|
|
4
|
+
|
|
5
|
+
[Documentation and live playground](https://www.m2toolkit.com/npm-packages/valiq-js)
|
|
6
|
+
|
|
7
|
+
valiq-js is a TypeScript-first validation library for JavaScript and TypeScript. You describe your data
|
|
8
|
+
once and get a runtime validator, an inferred TypeScript type, structured errors that are safe to
|
|
9
|
+
send to clients or write to logs, and JSON Schema in both directions.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { v, type Infer } from 'valiq-js';
|
|
13
|
+
|
|
14
|
+
const User = v.object({
|
|
15
|
+
id: v.string().uuid(),
|
|
16
|
+
email: v.string().email(),
|
|
17
|
+
age: v.number().int().min(18),
|
|
18
|
+
roles: v.array(v.enum(['admin', 'editor', 'viewer'])).default(['viewer']),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
type User = Infer<typeof User>;
|
|
22
|
+
// { id: string; email: string; age: number; roles: ("admin" | "editor" | "viewer")[] }
|
|
23
|
+
|
|
24
|
+
const result = User.safeParse({ id: '7f1c1f8e-2a4b-4c55-9a51-0d6c1b0f4e2a', email: 'nope', age: 30 });
|
|
25
|
+
if (result.success) {
|
|
26
|
+
const user: User = result.data; // roles defaults to ['viewer']
|
|
27
|
+
} else {
|
|
28
|
+
console.log(result.error.issues);
|
|
29
|
+
// [{ code: 'invalid_format', path: ['email'], message: 'Invalid email address', format: 'email' }, ...]
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Why valiq-js
|
|
34
|
+
|
|
35
|
+
- **One library for TypeScript schemas and JSON Schema.** Build schemas in TypeScript, or compile
|
|
36
|
+
existing JSON Schema documents (2020-12, 2019-09, draft-07) with
|
|
37
|
+
[`fromJSONSchema`](docs/json-schema.md). Both produce the same kind of schema with the same API,
|
|
38
|
+
and they nest inside each other. Export any schema with `toJSONSchema` for OpenAPI or other
|
|
39
|
+
services.
|
|
40
|
+
- **Errors you can act on.** Every issue has a stable `code`, the exact `path`, a readable
|
|
41
|
+
`message` and the constraint that failed (`minimum`, `format`, `keys`...). Messages never contain
|
|
42
|
+
the submitted value, so issues are safe to log or return in an API response. JSON Schema issues
|
|
43
|
+
also carry `keyword` and `schemaPath`.
|
|
44
|
+
- **Strict about schemas.** In JSON Schema mode, `{ "maxLenght": 10 }` throws
|
|
45
|
+
`Unknown keyword "maxLenght" (did you mean "maxLength"?)` at startup instead of silently
|
|
46
|
+
accepting everything.
|
|
47
|
+
- **Strong type inference.** Optional keys, defaults (input and output types differ), unions,
|
|
48
|
+
tuples, records, brands, transforms, and JSON Schema literals declared `as const` are all inferred.
|
|
49
|
+
- **Validation, not hidden transformation.** Valid input is returned as the same object. Nothing
|
|
50
|
+
is coerced, copied or stripped unless you ask for it (`v.coerce.*`, `.default()`, `.strip()`,
|
|
51
|
+
`.transform()`). Input is never mutated.
|
|
52
|
+
- **Safe by default.** Resistant to prototype pollution and ReDoS in built-in formats, with
|
|
53
|
+
resource limits for recursion depth, issue count and oversized arrays. No `eval` or
|
|
54
|
+
`new Function`, so it works under a strict Content Security Policy.
|
|
55
|
+
- **Works with your stack.** Implements [Standard Schema](https://standardschema.dev) and Standard
|
|
56
|
+
JSON Schema, so libraries that accept Standard Schema (such as tRPC, TanStack Form, React Hook
|
|
57
|
+
Form's Standard Schema resolver and Hono's standard validator) accept valiq-js schemas directly.
|
|
58
|
+
- **Zero dependencies.** ESM and CommonJS, Node.js 18+ and modern browsers. About 9 KB min+gzip for
|
|
59
|
+
typical builder usage.
|
|
60
|
+
|
|
61
|
+
valiq-js does not try to beat Ajv at raw throughput: Ajv generates code and is about 3× faster on
|
|
62
|
+
valid data. See [Performance](#performance) for measured numbers.
|
|
63
|
+
|
|
64
|
+
## Installation
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
npm install valiq-js
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Requirements: Node.js 18 or later, or any modern browser (ES2022). TypeScript 5.0+ is recommended
|
|
71
|
+
for inference; `strict` mode is expected.
|
|
72
|
+
|
|
73
|
+
## Quick start
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { v } from 'valiq-js';
|
|
77
|
+
|
|
78
|
+
const Signup = v.object({
|
|
79
|
+
name: v.string().trim().min(2),
|
|
80
|
+
email: v.string().trim().toLowerCase().email(),
|
|
81
|
+
password: v.string().min(12),
|
|
82
|
+
newsletter: v.boolean().default(false),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// parse(): returns the data or throws a ValiqError
|
|
86
|
+
const signup = Signup.parse({ name: ' Ada ', email: 'ADA@example.com', password: 'correct horse battery' });
|
|
87
|
+
// { name: 'Ada', email: 'ada@example.com', password: '...', newsletter: false }
|
|
88
|
+
|
|
89
|
+
// safeParse(): never throws for invalid data
|
|
90
|
+
const result = Signup.safeParse({ name: 'A' });
|
|
91
|
+
if (!result.success) {
|
|
92
|
+
result.error.flatten().fields;
|
|
93
|
+
// { name: ['Expected at least 2 characters'], email: ['Required'], password: ['Required'] }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// is(): a fast type guard when you only need yes or no
|
|
97
|
+
const payload: unknown = { name: 'Ada', email: 'ada@example.com', password: 'correct horse battery' };
|
|
98
|
+
if (Signup.is(payload)) {
|
|
99
|
+
payload.email; // narrowed
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Core API
|
|
104
|
+
|
|
105
|
+
| Schema | Accepts |
|
|
106
|
+
| ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
107
|
+
| `v.string()` | strings; `min`, `max`, `length` (code points), `regex`, formats |
|
|
108
|
+
| `v.number()` | finite numbers; `int`, `min`, `max`, `gt`, `lt`, `multipleOf` |
|
|
109
|
+
| `v.boolean()`, `v.null()`, `v.undefined()` | that exact type |
|
|
110
|
+
| `v.date()` | valid `Date` instances; `min`, `max` |
|
|
111
|
+
| `v.literal(x)`, `v.enum([...])` | one fixed value / one of several (also accepts TypeScript enums) |
|
|
112
|
+
| `v.object({...})` | objects; `.strict()`, `.strip()`, `.catchall()`, `.extend()`, `.pick()`, `.omit()`, `.partial()`, `.required()`, `.keyof()` |
|
|
113
|
+
| `v.array(item)`, `v.tuple([...])` | arrays; `min`, `max`, `length`, `unique` / fixed positions, `.rest()` |
|
|
114
|
+
| `v.record(key, value)` | dictionaries with validated keys and values |
|
|
115
|
+
| `v.union([...])`, `v.intersection([...])` | any one / all of several schemas |
|
|
116
|
+
| `v.lazy(() => schema)` | recursive structures |
|
|
117
|
+
| `v.unknown()`, `v.never()`, `v.instance(C)`, `v.custom(fn)` | escape hatches |
|
|
118
|
+
| `v.coerce.number()`, `.boolean()`, `.date()` | explicit, strict conversion from strings |
|
|
119
|
+
|
|
120
|
+
Every schema has `parse`, `safeParse`, `is`, `optional`, `nullable`, `nullish`, `default`,
|
|
121
|
+
`refine`, `transform`, `pipe`, `brand` and `meta`. Schemas are immutable: each method returns a
|
|
122
|
+
new schema. See the [API reference](docs/api.md).
|
|
123
|
+
|
|
124
|
+
String formats: `email`, `url` (http/https by default), `uuid`, `ipv4`, `ipv6`, `hostname`,
|
|
125
|
+
`isoDate`, `isoTime`, `isoDateTime`, plus `startsWith`, `endsWith`, `includes`, and the transforms
|
|
126
|
+
`trim`, `toLowerCase` and `toUpperCase`. The exact rules are in [docs/schemas.md](docs/schemas.md#string-formats).
|
|
127
|
+
|
|
128
|
+
## TypeScript inference
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import { v, type Infer, type Input } from 'valiq-js';
|
|
132
|
+
|
|
133
|
+
const Settings = v.object({
|
|
134
|
+
theme: v.enum(['light', 'dark']).default('light'),
|
|
135
|
+
fontSize: v.number().int().min(8).optional(),
|
|
136
|
+
shortcuts: v.record(v.string(), v.string()),
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
type Settings = Infer<typeof Settings>;
|
|
140
|
+
// { theme: 'light' | 'dark'; shortcuts: Record<string, string>; fontSize?: number | undefined }
|
|
141
|
+
|
|
142
|
+
type SettingsInput = Input<typeof Settings>;
|
|
143
|
+
// { shortcuts: Record<string, string>; theme?: 'light' | 'dark' | undefined; fontSize?: number | undefined }
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Discriminated unions narrow as expected, brands give you nominal IDs
|
|
147
|
+
(`v.string().uuid().brand<'UserId'>()`), and recursive schemas use `v.lazy` with an explicit
|
|
148
|
+
annotation. See [docs/typescript.md](docs/typescript.md).
|
|
149
|
+
|
|
150
|
+
## Errors
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import { v } from 'valiq-js';
|
|
154
|
+
|
|
155
|
+
const Order = v.object({
|
|
156
|
+
items: v.array(v.object({ sku: v.string().min(3), qty: v.number().int().min(1) })).min(1),
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const result = Order.safeParse({ items: [{ sku: 'AB', qty: 0 }] });
|
|
160
|
+
if (!result.success) {
|
|
161
|
+
result.error.issues;
|
|
162
|
+
// [
|
|
163
|
+
// { code: 'too_small', path: ['items', 0, 'sku'], message: 'Expected at least 3 characters',
|
|
164
|
+
// origin: 'string', minimum: 3, inclusive: true },
|
|
165
|
+
// { code: 'too_small', path: ['items', 0, 'qty'], message: 'Expected a number greater than or equal to 1',
|
|
166
|
+
// origin: 'number', minimum: 1, inclusive: true },
|
|
167
|
+
// ]
|
|
168
|
+
result.error.format();
|
|
169
|
+
// ✖ Expected at least 3 characters
|
|
170
|
+
// → at items[0].sku
|
|
171
|
+
// ✖ Expected a number greater than or equal to 1
|
|
172
|
+
// → at items[0].qty
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Issue codes: `invalid_type`, `required`, `invalid_value`, `too_small`, `too_big`,
|
|
177
|
+
`not_multiple_of`, `invalid_format`, `invalid_pattern`, `unrecognized_keys`, `invalid_key`,
|
|
178
|
+
`invalid_union`, `not_unique`, `forbidden`, `too_deep`, `custom`. Messages can be customised per
|
|
179
|
+
check (`.min(3, 'Too short')`) or globally for localisation (`safeParse(data, { messages })`). See
|
|
180
|
+
[docs/errors.md](docs/errors.md).
|
|
181
|
+
|
|
182
|
+
## JSON Schema
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
import { fromJSONSchema } from 'valiq-js/json-schema';
|
|
186
|
+
|
|
187
|
+
const Product = fromJSONSchema({
|
|
188
|
+
type: 'object',
|
|
189
|
+
properties: {
|
|
190
|
+
sku: { type: 'string', pattern: '^[A-Z]{3}-\\d{4}$' },
|
|
191
|
+
price: { type: 'number', minimum: 0 },
|
|
192
|
+
},
|
|
193
|
+
required: ['sku', 'price'],
|
|
194
|
+
additionalProperties: false,
|
|
195
|
+
} as const);
|
|
196
|
+
|
|
197
|
+
Product.parse({ sku: 'ABC-1234', price: 10 }); // typed { sku: string; price: number }
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Supported: draft 2020-12, 2019-09 and draft-07, including `$ref` (local, `$id`, `$anchor`,
|
|
201
|
+
recursive, and pre-registered schemas through the `schemas` option), all composition keywords,
|
|
202
|
+
`if`/`then`/`else`, dependencies, `contains` with min/max, and 15 formats. Against the official
|
|
203
|
+
[JSON-Schema-Test-Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite), valiq-js passes
|
|
204
|
+
**every test it runs** in the required suites: 1052 for 2020-12, 1032 for 2019-09 and 925 for
|
|
205
|
+
draft-07.
|
|
206
|
+
|
|
207
|
+
**Not supported yet:** `unevaluatedProperties`, `unevaluatedItems`, `$dynamicRef` and
|
|
208
|
+
`$recursiveRef` (schemas using them throw `JSONSchemaError` instead of being half-applied), and
|
|
209
|
+
the internationalised formats (`idn-email`, `idn-hostname`, `iri`, `iri-reference`). Remote
|
|
210
|
+
schemas are never fetched. Full details, options and the conformance table are in
|
|
211
|
+
[docs/json-schema.md](docs/json-schema.md).
|
|
212
|
+
|
|
213
|
+
`toJSONSchema(schema)` exports builder schemas to 2020-12 or draft-07. The output is checked
|
|
214
|
+
against Ajv in the test suite.
|
|
215
|
+
|
|
216
|
+
## Custom validation
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
import { v } from 'valiq-js';
|
|
220
|
+
|
|
221
|
+
const Password = v
|
|
222
|
+
.string()
|
|
223
|
+
.min(12)
|
|
224
|
+
.refine((s) => /[0-9]/.test(s), 'Include at least one digit');
|
|
225
|
+
|
|
226
|
+
const Signup = v
|
|
227
|
+
.object({ password: Password, confirm: v.string() })
|
|
228
|
+
.refine((form) => form.password === form.confirm, { message: 'Passwords do not match', path: ['confirm'] });
|
|
229
|
+
|
|
230
|
+
const Amount = v
|
|
231
|
+
.string()
|
|
232
|
+
.transform((text) => Number(text.replaceAll(',', '')))
|
|
233
|
+
.pipe(v.number().min(0));
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Node.js, browsers and frameworks
|
|
237
|
+
|
|
238
|
+
valiq-js has no dependencies and uses only standard JavaScript APIs (no Node.js built-ins), so it
|
|
239
|
+
runs in Node.js and browsers and should run unchanged in Deno, Bun and edge runtimes (only Node.js
|
|
240
|
+
is covered by the test suite today). It ships ESM and CommonJS builds with type declarations for
|
|
241
|
+
both.
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
// Any library that accepts a Standard Schema accepts a valiq-js schema:
|
|
245
|
+
import { v } from 'valiq-js';
|
|
246
|
+
const Login = v.object({ email: v.string().email(), password: v.string().min(8) });
|
|
247
|
+
const result = await Login['~standard'].validate({ email: 'dev@example.com', password: 'hunter22!' });
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Working examples: [API request](examples/api-request.ts), [environment config](examples/env-config.ts),
|
|
251
|
+
[registration form](examples/registration-form.ts), [nested JSON](examples/nested-json.ts),
|
|
252
|
+
[JSON Schema](examples/json-schema.ts), [custom validators](examples/custom-validators.ts),
|
|
253
|
+
[schema reuse](examples/compiled-reuse.ts) and [Standard Schema](examples/standard-schema.ts).
|
|
254
|
+
React usage is covered in [docs/examples.md](docs/examples.md#react-forms).
|
|
255
|
+
|
|
256
|
+
## Performance
|
|
257
|
+
|
|
258
|
+
Measured with `npm run benchmark` (tinybench, 1 s per task) on Node.js 22.23.1 on an Apple M4,
|
|
259
|
+
comparing valiq-js 0.1.0, ajv 8.20.0, zod 4.6.7 and valibot 1.5.0. Every library validates the same
|
|
260
|
+
rules, and all accept unknown keys so that none of them copies the input. Higher is better.
|
|
261
|
+
|
|
262
|
+
| Workload | valiq-js | valiq-js (JSON Schema) | Ajv | Zod | Valibot |
|
|
263
|
+
| --------------------------------------- | -------: | ---------------------: | ------: | -----: | ------: |
|
|
264
|
+
| Valid object (nested, 5 fields), ops/s | 7.1 M | 4.1 M | 23.3 M | 3.5 M | 2.6 M |
|
|
265
|
+
| Invalid object, all errors, ops/s | 1.0 M | 0.82 M | 16.6 M | 1.4 M | 1.6 M |
|
|
266
|
+
| Valid array of 1,000 objects, ops/s | 7,832 | 4,396 | 38,307 | 3,576 | 2,723 |
|
|
267
|
+
| Discriminated union, 300 items, ops/s | 68,286 | 27,308 (`oneOf`) | 114,957 | 75,153 | 16,970 |
|
|
268
|
+
| Define schema + first validation, ops/s | 520 k | 42 k | 422 | 17 k | 899 k |
|
|
269
|
+
|
|
270
|
+
In short: Ajv is the fastest validator on hot paths because it generates JavaScript code. valiq-js
|
|
271
|
+
does not generate code, which makes it CSP-safe. It is about 2× faster than Zod and 2.7× faster
|
|
272
|
+
than Valibot on valid objects, and it starts up three orders of magnitude faster than Ajv's
|
|
273
|
+
compiler (useful for serverless cold starts). On invalid input, Zod and Valibot are faster than
|
|
274
|
+
valiq-js when every error is collected. Numbers vary by machine and workload. Run the suite yourself;
|
|
275
|
+
the methodology is in [docs/performance.md](docs/performance.md).
|
|
276
|
+
|
|
277
|
+
Bundle size (min+gzip, measured with `npm run size -- --compare`): an object schema with string,
|
|
278
|
+
number and array checks costs 9.4 KB with valiq-js, 20.7 KB with Zod, 5.5 KB with `zod/mini`,
|
|
279
|
+
1.5 KB with Valibot and 34.6 KB with Ajv (no formats). Everything in `valiq-js` is 11.5 KB;
|
|
280
|
+
`fromJSONSchema` adds about 6 KB.
|
|
281
|
+
|
|
282
|
+
## Security
|
|
283
|
+
|
|
284
|
+
Validation checks shape and constraints. It is not sanitisation (it does not make strings safe for
|
|
285
|
+
HTML or SQL) and not authorisation. valiq-js is designed for untrusted input:
|
|
286
|
+
|
|
287
|
+
- Own properties only: values inherited from a polluted `Object.prototype` are treated as missing,
|
|
288
|
+
and `__proto__` keys are copied as data, never as prototypes.
|
|
289
|
+
- Input is never mutated. Object and array defaults are cloned for each use.
|
|
290
|
+
- Built-in formats use linear-time checks. User-supplied patterns are your responsibility; the
|
|
291
|
+
JSON Schema `regExp` option lets you plug in RE2.
|
|
292
|
+
- Limits: `maxDepth` (512) for recursive schemas, `maxIssues` (100); arrays above `max()` /
|
|
293
|
+
`maxItems` are rejected without visiting their items, and strings above `max()` / `maxLength`
|
|
294
|
+
skip their remaining checks.
|
|
295
|
+
- Do not compile JSON Schemas from untrusted sources without reviewing them: patterns run as
|
|
296
|
+
regular expressions.
|
|
297
|
+
|
|
298
|
+
See [docs/security.md](docs/security.md), and [SECURITY.md](SECURITY.md) to report a vulnerability.
|
|
299
|
+
|
|
300
|
+
## Documentation
|
|
301
|
+
|
|
302
|
+
The [documentation site](https://www.m2toolkit.com/npm-packages/valiq-js) has every page below, search
|
|
303
|
+
and a live playground.
|
|
304
|
+
|
|
305
|
+
- [Getting started](docs/getting-started.md)
|
|
306
|
+
- [Core concepts](docs/concepts.md): parse vs safeParse vs is, input and output, immutability
|
|
307
|
+
- [Schemas](docs/schemas.md): every schema type, method and format rule
|
|
308
|
+
- [TypeScript](docs/typescript.md)
|
|
309
|
+
- [Errors](docs/errors.md)
|
|
310
|
+
- [JSON Schema](docs/json-schema.md)
|
|
311
|
+
- [Custom validation](docs/custom-validation.md)
|
|
312
|
+
- [Performance](docs/performance.md)
|
|
313
|
+
- [Security](docs/security.md)
|
|
314
|
+
- [API reference](docs/api.md)
|
|
315
|
+
- [Examples](docs/examples.md)
|
|
316
|
+
- [Migrating from Zod or Ajv](docs/migration.md)
|
|
317
|
+
- [Troubleshooting and FAQ](docs/troubleshooting.md)
|
|
318
|
+
- [Roadmap](docs/roadmap.md)
|
|
319
|
+
|
|
320
|
+
## FAQ
|
|
321
|
+
|
|
322
|
+
**Should I use valiq-js or Ajv?** If you only validate JSON Schema documents on a very hot path, Ajv
|
|
323
|
+
is faster. Pick valiq-js if you want TypeScript-first schemas and JSON Schema in one library, errors
|
|
324
|
+
designed for users, strict schema checking with suggestions, fast startup or CSP compatibility.
|
|
325
|
+
|
|
326
|
+
**Does valiq-js strip unknown properties?** Not by default (the same as JSON Schema and Ajv). Use
|
|
327
|
+
`.strip()` to drop them, or `.strict()` to reject them. For API input, one of the two is
|
|
328
|
+
recommended.
|
|
329
|
+
|
|
330
|
+
**Is it production-ready?** Version 0.1 is a first release. The behaviour described here is tested
|
|
331
|
+
(300+ tests, the official JSON Schema test suite, property-based tests against Ajv), but the API may
|
|
332
|
+
still change before 1.0, following semver for 0.x (breaking changes bump the minor version).
|
|
333
|
+
|
|
334
|
+
More questions are answered in [docs/troubleshooting.md](docs/troubleshooting.md).
|
|
335
|
+
|
|
336
|
+
## Contributing
|
|
337
|
+
|
|
338
|
+
Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for the development setup.
|
|
339
|
+
`npm run verify` runs every check that CI runs.
|
|
340
|
+
|
|
341
|
+
## License
|
|
342
|
+
|
|
343
|
+
[MIT](LICENSE)
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
## Supported versions
|
|
4
|
+
|
|
5
|
+
Only the latest published minor version receives security fixes while valiq-js is in 0.x.
|
|
6
|
+
|
|
7
|
+
## Reporting a vulnerability
|
|
8
|
+
|
|
9
|
+
Please report vulnerabilities privately. Do not open a public issue.
|
|
10
|
+
|
|
11
|
+
- Use GitHub's private vulnerability reporting ("Report a vulnerability" under the repository's
|
|
12
|
+
Security tab), or
|
|
13
|
+
- email the maintainer at mohsinziadev@gmail.com with "valiq-js security" in the subject.
|
|
14
|
+
|
|
15
|
+
Include the valiq-js version, a minimal schema and input that reproduce the problem, and the impact
|
|
16
|
+
you expect (for example "validation accepts X" or "validation hangs on Y"). You should get an
|
|
17
|
+
acknowledgement within 5 working days. Once a fix is released, the advisory credits the reporter
|
|
18
|
+
unless they prefer otherwise.
|
|
19
|
+
|
|
20
|
+
## Scope
|
|
21
|
+
|
|
22
|
+
In scope: validation that accepts data it should reject, prototype pollution, input mutation,
|
|
23
|
+
denial of service through built-in checks (catastrophic regular expressions, unbounded recursion
|
|
24
|
+
or allocation), and information leaks through error messages.
|
|
25
|
+
|
|
26
|
+
Out of scope: regular expressions and callbacks supplied by the application (see
|
|
27
|
+
[docs/security.md](docs/security.md)), and JSON Schemas from untrusted sources compiled without
|
|
28
|
+
review.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// \0rolldown/runtime.js
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __exportAll = (all, no_symbols) => {
|
|
4
|
+
let target = {};
|
|
5
|
+
for (var name in all) __defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true
|
|
8
|
+
});
|
|
9
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
10
|
+
return target;
|
|
11
|
+
};
|
|
12
|
+
exports.__exportAll = __exportAll;
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// src/checks/formats.ts
|
|
2
|
+
const EMAIL = /^[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$/;
|
|
3
|
+
/**
|
|
4
|
+
* Practical email check: dot-atom local part (max 64 chars), a domain with at least one dot,
|
|
5
|
+
* total length at most 254. Quoted local parts, IP-literal domains and non-ASCII are rejected.
|
|
6
|
+
*/
|
|
7
|
+
function isEmail(value) {
|
|
8
|
+
if (value.length > 254) return false;
|
|
9
|
+
const at = value.lastIndexOf("@");
|
|
10
|
+
if (at < 1 || at > 64) return false;
|
|
11
|
+
return EMAIL.test(value);
|
|
12
|
+
}
|
|
13
|
+
const DOT_STRING = /^[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*$/;
|
|
14
|
+
const QUOTED_STRING = /^"(?:[\x20\x21\x23-\x5b\x5d-\x7e]|\\[\x20-\x7e])*"$/;
|
|
15
|
+
const GENERAL_LITERAL = /^[A-Za-z0-9-]*[A-Za-z0-9]:[\x21-\x5a\x5e-\x7e]+$/;
|
|
16
|
+
/**
|
|
17
|
+
* RFC 5321 `Mailbox`, as required by JSON Schema's `email` format: dot-string or quoted local
|
|
18
|
+
* part, and a domain or an address literal (`[192.0.2.1]`, `[IPv6:...]`). ASCII only.
|
|
19
|
+
*/
|
|
20
|
+
function isMailbox(value) {
|
|
21
|
+
if (value.length > 320) return false;
|
|
22
|
+
const at = value.lastIndexOf("@");
|
|
23
|
+
if (at < 1) return false;
|
|
24
|
+
const local = value.slice(0, at);
|
|
25
|
+
const domain = value.slice(at + 1);
|
|
26
|
+
if (!(DOT_STRING.test(local) || QUOTED_STRING.test(local))) return false;
|
|
27
|
+
if (domain.startsWith("[") && domain.endsWith("]")) {
|
|
28
|
+
const literal = domain.slice(1, -1);
|
|
29
|
+
if (/^ipv6:/i.test(literal)) return isIPv6(literal.slice(5));
|
|
30
|
+
return isIPv4(literal) || !literal.includes(".") && GENERAL_LITERAL.test(literal) && !/^ipv6:/i.test(literal);
|
|
31
|
+
}
|
|
32
|
+
return isHostname(domain);
|
|
33
|
+
}
|
|
34
|
+
const UUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
35
|
+
/** RFC 9562 textual form, any version or variant (including the nil and max UUIDs). */
|
|
36
|
+
function isUUID(value) {
|
|
37
|
+
return value.length === 36 && UUID.test(value);
|
|
38
|
+
}
|
|
39
|
+
const IPV4 = /^(?: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}$/;
|
|
40
|
+
/** Dotted-quad IPv4; leading zeros are rejected because they are ambiguous (octal). */
|
|
41
|
+
function isIPv4(value) {
|
|
42
|
+
return value.length <= 15 && IPV4.test(value);
|
|
43
|
+
}
|
|
44
|
+
const HEX_GROUP = /^[0-9a-fA-F]{1,4}$/;
|
|
45
|
+
/** RFC 4291 text form, including `::` compression and an embedded IPv4 tail. No zone IDs. */
|
|
46
|
+
function isIPv6(value) {
|
|
47
|
+
if (value.length < 2 || value.length > 45) return false;
|
|
48
|
+
const halves = value.split("::");
|
|
49
|
+
if (halves.length > 2) return false;
|
|
50
|
+
const groups = [];
|
|
51
|
+
const parts = halves.map((half) => half === "" ? [] : half.split(":"));
|
|
52
|
+
for (const part of parts) groups.push(...part);
|
|
53
|
+
let count = groups.length;
|
|
54
|
+
const last = groups[groups.length - 1];
|
|
55
|
+
if (last !== void 0 && last.includes(".")) {
|
|
56
|
+
if (!isIPv4(last)) return false;
|
|
57
|
+
groups.pop();
|
|
58
|
+
count += 1;
|
|
59
|
+
}
|
|
60
|
+
for (const group of groups) if (!HEX_GROUP.test(group)) return false;
|
|
61
|
+
return halves.length === 2 ? count <= 7 : count === 8;
|
|
62
|
+
}
|
|
63
|
+
const LABEL = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$/;
|
|
64
|
+
/** RFC 1123 hostname: dot-separated labels of 1-63 letters, digits and inner hyphens. */
|
|
65
|
+
function isHostname(value) {
|
|
66
|
+
if (value.length === 0 || value.length > 253) return false;
|
|
67
|
+
for (const label of value.split(".")) {
|
|
68
|
+
if (!LABEL.test(label)) return false;
|
|
69
|
+
if (label[2] === "-" && label[3] === "-" && label.slice(0, 2).toLowerCase() !== "xn") return false;
|
|
70
|
+
}
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
const DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
74
|
+
const DAYS = [
|
|
75
|
+
0,
|
|
76
|
+
31,
|
|
77
|
+
28,
|
|
78
|
+
31,
|
|
79
|
+
30,
|
|
80
|
+
31,
|
|
81
|
+
30,
|
|
82
|
+
31,
|
|
83
|
+
31,
|
|
84
|
+
30,
|
|
85
|
+
31,
|
|
86
|
+
30,
|
|
87
|
+
31
|
|
88
|
+
];
|
|
89
|
+
function isLeap(year) {
|
|
90
|
+
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
91
|
+
}
|
|
92
|
+
/** RFC 3339 full-date `YYYY-MM-DD` with a real calendar day. */
|
|
93
|
+
function isDate(value) {
|
|
94
|
+
const m = DATE.exec(value);
|
|
95
|
+
if (m === null) return false;
|
|
96
|
+
const year = Number(m[1]);
|
|
97
|
+
const month = Number(m[2]);
|
|
98
|
+
const day = Number(m[3]);
|
|
99
|
+
if (month < 1 || month > 12 || day < 1) return false;
|
|
100
|
+
return day <= (month === 2 && isLeap(year) ? 29 : DAYS[month]);
|
|
101
|
+
}
|
|
102
|
+
const RFC_TIME = /^(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?([Zz]|([+-])(\d{2}):(\d{2}))$/;
|
|
103
|
+
/**
|
|
104
|
+
* RFC 3339 full-time `HH:MM:SS[.frac](Z|±HH:MM)`. A leap second (`:60`) is accepted only when the
|
|
105
|
+
* time is 23:59 UTC.
|
|
106
|
+
*/
|
|
107
|
+
function isRFC3339Time(value) {
|
|
108
|
+
const m = RFC_TIME.exec(value);
|
|
109
|
+
if (m === null) return false;
|
|
110
|
+
const hour = Number(m[1]);
|
|
111
|
+
const minute = Number(m[2]);
|
|
112
|
+
const second = Number(m[3]);
|
|
113
|
+
const offH = m[6] === void 0 ? 0 : Number(m[6]);
|
|
114
|
+
const offM = m[7] === void 0 ? 0 : Number(m[7]);
|
|
115
|
+
if (hour > 23 || minute > 59 || second > 60 || offH > 23 || offM > 59) return false;
|
|
116
|
+
if (second === 60) {
|
|
117
|
+
const sign = m[5] === "-" ? 1 : -1;
|
|
118
|
+
return ((hour * 60 + minute + sign * (offH * 60 + offM)) % 1440 + 1440) % 1440 === 1439;
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
/** RFC 3339 date-time: full-date, `T`, full-time. */
|
|
123
|
+
function isRFC3339DateTime(value) {
|
|
124
|
+
const separator = value[10];
|
|
125
|
+
return (separator === "T" || separator === "t") && isDate(value.slice(0, 10)) && isRFC3339Time(value.slice(11));
|
|
126
|
+
}
|
|
127
|
+
const LOCAL_TIME = /^(\d{2}):(\d{2})(?::(\d{2})(?:\.\d{1,9})?)?$/;
|
|
128
|
+
/** Local time `HH:MM`, `HH:MM:SS` or `HH:MM:SS.fff`, as produced by `<input type="time">`. */
|
|
129
|
+
function isLocalTime(value) {
|
|
130
|
+
const m = LOCAL_TIME.exec(value);
|
|
131
|
+
if (m === null) return false;
|
|
132
|
+
return Number(m[1]) <= 23 && Number(m[2]) <= 59 && (m[3] === void 0 || Number(m[3]) <= 59);
|
|
133
|
+
}
|
|
134
|
+
/** Local date-time `YYYY-MM-DDTHH:MM[:SS[.fff]]`, as produced by `<input type="datetime-local">`. */
|
|
135
|
+
function isLocalDateTime(value) {
|
|
136
|
+
return value[10] === "T" && isDate(value.slice(0, 10)) && isLocalTime(value.slice(11));
|
|
137
|
+
}
|
|
138
|
+
const DURATION = /^P(?:(?:\d+Y(?:\d+M(?:\d+D)?)?|\d+M(?:\d+D)?|\d+D)(?:T(?:\d+H(?:\d+M(?:\d+S)?)?|\d+M(?:\d+S)?|\d+S))?|T(?:\d+H(?:\d+M(?:\d+S)?)?|\d+M(?:\d+S)?|\d+S)|\d+W)$/;
|
|
139
|
+
/** RFC 3339 Appendix A (ISO 8601) duration, e.g. `P1Y2M10DT2H30M` or `P3W`. */
|
|
140
|
+
function isDuration(value) {
|
|
141
|
+
return DURATION.test(value);
|
|
142
|
+
}
|
|
143
|
+
const PCT = "%[0-9A-Fa-f]{2}";
|
|
144
|
+
const UNRESERVED = "A-Za-z0-9\\-._~!$&'()*+,;=";
|
|
145
|
+
const PCHAR = `(?:[${UNRESERVED}:@]|${PCT})`;
|
|
146
|
+
const AUTHORITY = `(?:${`(?:[${UNRESERVED}:]|${PCT})*`}@)?(?:\\[[0-9A-Fa-f:.vV]+\\]|${`(?:[${UNRESERVED}]|${PCT})*`})(?::\\d*)?`;
|
|
147
|
+
const PATH_ABEMPTY = `(?:/${PCHAR}*)*`;
|
|
148
|
+
const PATH_ABSOLUTE = `/(?:${PCHAR}+${PATH_ABEMPTY})?`;
|
|
149
|
+
const PATH_ROOTLESS = `${PCHAR}+${PATH_ABEMPTY}`;
|
|
150
|
+
const PATH_NOSCHEME = `(?:[${UNRESERVED}@]|${PCT})+${PATH_ABEMPTY}`;
|
|
151
|
+
const QUERY = `(?:\\?(?:${PCHAR}|[/?])*)?`;
|
|
152
|
+
const FRAGMENT = `(?:#(?:${PCHAR}|[/?])*)?`;
|
|
153
|
+
const HIER = `(?://${AUTHORITY}${PATH_ABEMPTY}|${PATH_ABSOLUTE}|${PATH_ROOTLESS}|)`;
|
|
154
|
+
const RELATIVE = `(?://${AUTHORITY}${PATH_ABEMPTY}|${PATH_ABSOLUTE}|${PATH_NOSCHEME}|)`;
|
|
155
|
+
const URI = /* @__PURE__ */ new RegExp(`^[A-Za-z][A-Za-z0-9+\\-.]*:${HIER}${QUERY}${FRAGMENT}$`);
|
|
156
|
+
const URI_REFERENCE = /* @__PURE__ */ new RegExp(`^(?:[A-Za-z][A-Za-z0-9+\\-.]*:${HIER}|${RELATIVE})${QUERY}${FRAGMENT}$`);
|
|
157
|
+
const IP_FUTURE = /^v[0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&'()*+,;=:]+$/;
|
|
158
|
+
/** The bracketed host of a URI, if any, must be a valid IPv6 address or IPvFuture literal. */
|
|
159
|
+
function validIPLiteral(value) {
|
|
160
|
+
const open = value.indexOf("[");
|
|
161
|
+
if (open === -1) return true;
|
|
162
|
+
const close = value.indexOf("]", open);
|
|
163
|
+
if (close === -1) return false;
|
|
164
|
+
const literal = value.slice(open + 1, close);
|
|
165
|
+
return isIPv6(literal) || IP_FUTURE.test(literal);
|
|
166
|
+
}
|
|
167
|
+
/** RFC 3986 absolute URI (any scheme). */
|
|
168
|
+
function isURI(value) {
|
|
169
|
+
return URI.test(value) && validIPLiteral(value);
|
|
170
|
+
}
|
|
171
|
+
/** RFC 3986 URI-reference: an absolute URI or a relative reference. */
|
|
172
|
+
function isURIReference(value) {
|
|
173
|
+
return URI_REFERENCE.test(value) && validIPLiteral(value);
|
|
174
|
+
}
|
|
175
|
+
const HTTP_PROTOCOLS = ["http", "https"];
|
|
176
|
+
/**
|
|
177
|
+
* A URL that the WHATWG `URL` parser accepts, contains no whitespace, is at most 8192 characters
|
|
178
|
+
* and uses one of `protocols` (default `http` and `https`, compared case-insensitively).
|
|
179
|
+
*/
|
|
180
|
+
function isURL(value, protocols = HTTP_PROTOCOLS) {
|
|
181
|
+
if (value.length > 8192 || /\s/.test(value)) return false;
|
|
182
|
+
const colon = value.indexOf(":");
|
|
183
|
+
if (colon < 1) return false;
|
|
184
|
+
const scheme = value.slice(0, colon).toLowerCase();
|
|
185
|
+
if (!protocols.includes(scheme)) return false;
|
|
186
|
+
try {
|
|
187
|
+
new URL(value);
|
|
188
|
+
return true;
|
|
189
|
+
} catch {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/** JSON Pointer (RFC 6901). */
|
|
194
|
+
function isJSONPointer(value) {
|
|
195
|
+
return /^(?:\/(?:[^~/]|~[01])*)*$/.test(value);
|
|
196
|
+
}
|
|
197
|
+
/** Relative JSON Pointer (draft-handrews-relative-json-pointer-01). */
|
|
198
|
+
function isRelativeJSONPointer(value) {
|
|
199
|
+
return /^(?:0|[1-9]\d*)(?:#|(?:\/(?:[^~/]|~[01])*)*)$/.test(value);
|
|
200
|
+
}
|
|
201
|
+
const VARSPEC = "(?:\\w|%[0-9A-Fa-f]{2})(?:\\.?(?:\\w|%[0-9A-Fa-f]{2}))*(?::[1-9]\\d{0,3}|\\*)?";
|
|
202
|
+
const URI_TEMPLATE = /* @__PURE__ */ new RegExp(`^(?:[^\\x00-\\x20\\x7f"<>%\\\\^\`{|}]|%[0-9A-Fa-f]{2}|\\{[+#./;?&=,!@|]?${VARSPEC}(?:,${VARSPEC})*\\})*$`);
|
|
203
|
+
/** RFC 6570 URI Template (up to level 4). */
|
|
204
|
+
function isURITemplate(value) {
|
|
205
|
+
return URI_TEMPLATE.test(value);
|
|
206
|
+
}
|
|
207
|
+
/** A JavaScript regular expression source with the `u` flag (closest to ECMA-262 semantics). */
|
|
208
|
+
function isRegex(value) {
|
|
209
|
+
try {
|
|
210
|
+
new RegExp(value, "u");
|
|
211
|
+
return true;
|
|
212
|
+
} catch {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
exports.isDate = isDate;
|
|
217
|
+
exports.isDuration = isDuration;
|
|
218
|
+
exports.isEmail = isEmail;
|
|
219
|
+
exports.isHostname = isHostname;
|
|
220
|
+
exports.isIPv4 = isIPv4;
|
|
221
|
+
exports.isIPv6 = isIPv6;
|
|
222
|
+
exports.isJSONPointer = isJSONPointer;
|
|
223
|
+
exports.isLocalDateTime = isLocalDateTime;
|
|
224
|
+
exports.isLocalTime = isLocalTime;
|
|
225
|
+
exports.isMailbox = isMailbox;
|
|
226
|
+
exports.isRFC3339DateTime = isRFC3339DateTime;
|
|
227
|
+
exports.isRFC3339Time = isRFC3339Time;
|
|
228
|
+
exports.isRegex = isRegex;
|
|
229
|
+
exports.isRelativeJSONPointer = isRelativeJSONPointer;
|
|
230
|
+
exports.isURI = isURI;
|
|
231
|
+
exports.isURIReference = isURIReference;
|
|
232
|
+
exports.isURITemplate = isURITemplate;
|
|
233
|
+
exports.isURL = isURL;
|
|
234
|
+
exports.isUUID = isUUID;
|