@scalar/validation 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.
@@ -0,0 +1,91 @@
1
+ import { isObject } from './helpers/is-object'
2
+ import type { Schema } from './schema'
3
+
4
+ /**
5
+ * Validates that a given value matches the specified schema.
6
+ *
7
+ * The schema describes the expected structure/type of data.
8
+ * Supported schema types include:
9
+ * - 'any': Accepts any value.
10
+ * - 'number': Only numbers are valid.
11
+ * - 'string': Only strings are valid.
12
+ * - 'boolean': Only booleans are valid.
13
+ * - 'nullable': Only `null` is valid.
14
+ * - 'notDefined': Only `undefined` is valid.
15
+ * - 'array': Array with all items validated recursively.
16
+ * - 'record': Object with string/number keys and values, checked recursively.
17
+ * - 'object': Object with fixed property keys, each validated recursively.
18
+ * - 'union': Accepts if value matches any of the listed schemas.
19
+ * - 'literal': Exact match with a literal value.
20
+ * - 'recursive': Schema referring to itself for nested validation (e.g. trees).
21
+ * - 'evaluate': Transforms value then validates against an inner schema.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * import { number, object, string, validate } from '@scalar/validation'
26
+ *
27
+ * const schema = object({ id: number(), name: string() })
28
+ * validate(schema, { id: 1, name: 'Ada' }) // true
29
+ * validate(schema, { id: 1, name: 2 }) // false
30
+ * ```
31
+ *
32
+ * If schema is `undefined`, validation fails.
33
+ * Returns true if the value matches the schema, false otherwise.
34
+ */
35
+ export const validate = (schema: Schema | undefined, value: unknown): boolean => {
36
+ if (!schema) {
37
+ return false
38
+ }
39
+ if (schema.type === 'any') {
40
+ return true
41
+ }
42
+ if (schema.type === 'number') {
43
+ return typeof value === 'number' && !Number.isNaN(value) && Number.isFinite(value)
44
+ }
45
+ if (schema.type === 'string') {
46
+ return typeof value === 'string'
47
+ }
48
+ if (schema.type === 'boolean') {
49
+ return typeof value === 'boolean'
50
+ }
51
+ if (schema.type === 'nullable') {
52
+ return value === null
53
+ }
54
+ if (schema.type === 'notDefined') {
55
+ return value === undefined
56
+ }
57
+ if (schema.type === 'array') {
58
+ return Array.isArray(value) && value.every((item) => validate(schema.items, item))
59
+ }
60
+ if (schema.type === 'record') {
61
+ if (!isObject(value)) {
62
+ return false
63
+ }
64
+
65
+ const keys = Object.keys(value)
66
+ return keys.every((key) => validate(schema.key, key) && validate(schema.value, value[key]))
67
+ }
68
+ if (schema.type === 'object') {
69
+ if (!isObject(value)) {
70
+ return false
71
+ }
72
+ const schemaKeys = Object.keys(schema.properties)
73
+ return schemaKeys.every((key) => validate(schema.properties[key], value[key]))
74
+ }
75
+ if (schema.type === 'union') {
76
+ return schema.schemas.some((schema) => validate(schema, value))
77
+ }
78
+ if (schema.type === 'literal') {
79
+ return value === schema.value
80
+ }
81
+ if (schema.type === 'lazy') {
82
+ return validate(schema.schema(), value)
83
+ }
84
+ if (schema.type === 'evaluate') {
85
+ return validate(schema.schema, schema.expression(value))
86
+ }
87
+ // We need to assert here that schema has the type never so we know we handle all cases
88
+ const _exhaustive: never = schema
89
+ console.warn('Unknown schema type:', _exhaustive)
90
+ return false
91
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "include": ["src"],
4
+ "exclude": ["**/*.test.ts", "vite.config.ts"],
5
+ "compilerOptions": {
6
+ "outDir": "dist",
7
+ "declaration": true,
8
+ "declarationMap": true,
9
+ "noEmit": false
10
+ }
11
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "paths": {
5
+ "@/*": ["./src/*"],
6
+ "@test/*": ["./test/*"]
7
+ }
8
+ },
9
+ "include": ["src/**/*.ts", "playground/**/*.ts", "test/**/*.ts", "esbuild.ts", "vite.config.ts"]
10
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { resolve } from 'node:path'
2
+
3
+ import { defineConfig } from 'vite'
4
+
5
+ export default defineConfig({
6
+ plugins: [],
7
+ resolve: {
8
+ alias: {
9
+ '@': resolve(import.meta.dirname, './src'),
10
+ '@test': resolve(import.meta.dirname, './test'),
11
+ },
12
+ },
13
+ })