@atproto/lex-schema 0.0.11 → 0.0.13
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 +54 -0
- package/dist/core/$type.d.ts +149 -0
- package/dist/core/$type.d.ts.map +1 -1
- package/dist/core/$type.js +44 -0
- package/dist/core/$type.js.map +1 -1
- package/dist/core/record-key.d.ts +44 -0
- package/dist/core/record-key.d.ts.map +1 -1
- package/dist/core/record-key.js +30 -0
- package/dist/core/record-key.js.map +1 -1
- package/dist/core/result.d.ts +85 -4
- package/dist/core/result.d.ts.map +1 -1
- package/dist/core/result.js +60 -4
- package/dist/core/result.js.map +1 -1
- package/dist/core/schema.d.ts +232 -5
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +197 -4
- package/dist/core/schema.js.map +1 -1
- package/dist/core/string-format.d.ts +244 -11
- package/dist/core/string-format.d.ts.map +1 -1
- package/dist/core/string-format.js +150 -0
- package/dist/core/string-format.js.map +1 -1
- package/dist/core/types.d.ts +90 -3
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/validation-error.d.ts +60 -0
- package/dist/core/validation-error.d.ts.map +1 -1
- package/dist/core/validation-error.js +60 -0
- package/dist/core/validation-error.js.map +1 -1
- package/dist/core/validation-issue.d.ts +61 -0
- package/dist/core/validation-issue.d.ts.map +1 -1
- package/dist/core/validation-issue.js +54 -1
- package/dist/core/validation-issue.js.map +1 -1
- package/dist/core/validator.d.ts +356 -11
- package/dist/core/validator.d.ts.map +1 -1
- package/dist/core/validator.js +203 -4
- package/dist/core/validator.js.map +1 -1
- package/dist/helpers.d.ts +12 -28
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js.map +1 -1
- package/dist/schema/array.d.ts +46 -0
- package/dist/schema/array.d.ts.map +1 -1
- package/dist/schema/array.js +16 -1
- package/dist/schema/array.js.map +1 -1
- package/dist/schema/blob.d.ts +50 -2
- package/dist/schema/blob.d.ts.map +1 -1
- package/dist/schema/blob.js +44 -2
- package/dist/schema/blob.js.map +1 -1
- package/dist/schema/boolean.d.ts +29 -0
- package/dist/schema/boolean.d.ts.map +1 -1
- package/dist/schema/boolean.js +30 -1
- package/dist/schema/boolean.js.map +1 -1
- package/dist/schema/bytes.d.ts +39 -0
- package/dist/schema/bytes.d.ts.map +1 -1
- package/dist/schema/bytes.js +34 -1
- package/dist/schema/bytes.js.map +1 -1
- package/dist/schema/cid.d.ts +39 -0
- package/dist/schema/cid.d.ts.map +1 -1
- package/dist/schema/cid.js +35 -1
- package/dist/schema/cid.js.map +1 -1
- package/dist/schema/custom.d.ts +67 -1
- package/dist/schema/custom.d.ts.map +1 -1
- package/dist/schema/custom.js +55 -0
- package/dist/schema/custom.js.map +1 -1
- package/dist/schema/dict.d.ts +45 -0
- package/dist/schema/dict.d.ts.map +1 -1
- package/dist/schema/dict.js +46 -1
- package/dist/schema/dict.js.map +1 -1
- package/dist/schema/discriminated-union.d.ts +59 -0
- package/dist/schema/discriminated-union.d.ts.map +1 -1
- package/dist/schema/discriminated-union.js +47 -1
- package/dist/schema/discriminated-union.js.map +1 -1
- package/dist/schema/enum.d.ts +49 -0
- package/dist/schema/enum.d.ts.map +1 -1
- package/dist/schema/enum.js +49 -0
- package/dist/schema/enum.js.map +1 -1
- package/dist/schema/integer.d.ts +43 -0
- package/dist/schema/integer.d.ts.map +1 -1
- package/dist/schema/integer.js +38 -1
- package/dist/schema/integer.js.map +1 -1
- package/dist/schema/intersection.d.ts +55 -0
- package/dist/schema/intersection.d.ts.map +1 -1
- package/dist/schema/intersection.js +50 -0
- package/dist/schema/intersection.js.map +1 -1
- package/dist/schema/lex-map.d.ts +37 -0
- package/dist/schema/lex-map.d.ts.map +1 -0
- package/dist/schema/lex-map.js +60 -0
- package/dist/schema/lex-map.js.map +1 -0
- package/dist/schema/lex-value.d.ts +35 -0
- package/dist/schema/lex-value.d.ts.map +1 -0
- package/dist/schema/lex-value.js +87 -0
- package/dist/schema/lex-value.js.map +1 -0
- package/dist/schema/literal.d.ts +45 -0
- package/dist/schema/literal.d.ts.map +1 -1
- package/dist/schema/literal.js +45 -0
- package/dist/schema/literal.js.map +1 -1
- package/dist/schema/never.d.ts +43 -0
- package/dist/schema/never.d.ts.map +1 -1
- package/dist/schema/never.js +44 -1
- package/dist/schema/never.js.map +1 -1
- package/dist/schema/null.d.ts +30 -0
- package/dist/schema/null.d.ts.map +1 -1
- package/dist/schema/null.js +31 -1
- package/dist/schema/null.js.map +1 -1
- package/dist/schema/nullable.d.ts +42 -0
- package/dist/schema/nullable.d.ts.map +1 -1
- package/dist/schema/nullable.js +42 -0
- package/dist/schema/nullable.js.map +1 -1
- package/dist/schema/object.d.ts +57 -0
- package/dist/schema/object.d.ts.map +1 -1
- package/dist/schema/object.js +53 -1
- package/dist/schema/object.js.map +1 -1
- package/dist/schema/optional.d.ts +43 -0
- package/dist/schema/optional.d.ts.map +1 -1
- package/dist/schema/optional.js +43 -0
- package/dist/schema/optional.js.map +1 -1
- package/dist/schema/params.d.ts +96 -12
- package/dist/schema/params.d.ts.map +1 -1
- package/dist/schema/params.js +155 -21
- package/dist/schema/params.js.map +1 -1
- package/dist/schema/payload.d.ts +111 -15
- package/dist/schema/payload.d.ts.map +1 -1
- package/dist/schema/payload.js +73 -3
- package/dist/schema/payload.js.map +1 -1
- package/dist/schema/permission-set.d.ts +58 -0
- package/dist/schema/permission-set.d.ts.map +1 -1
- package/dist/schema/permission-set.js +50 -0
- package/dist/schema/permission-set.js.map +1 -1
- package/dist/schema/permission.d.ts +42 -0
- package/dist/schema/permission.d.ts.map +1 -1
- package/dist/schema/permission.js +39 -0
- package/dist/schema/permission.js.map +1 -1
- package/dist/schema/procedure.d.ts +64 -0
- package/dist/schema/procedure.d.ts.map +1 -1
- package/dist/schema/procedure.js +64 -0
- package/dist/schema/procedure.js.map +1 -1
- package/dist/schema/query.d.ts +55 -0
- package/dist/schema/query.d.ts.map +1 -1
- package/dist/schema/query.js +55 -0
- package/dist/schema/query.js.map +1 -1
- package/dist/schema/record.d.ts +76 -25
- package/dist/schema/record.d.ts.map +1 -1
- package/dist/schema/record.js +21 -0
- package/dist/schema/record.js.map +1 -1
- package/dist/schema/ref.d.ts +51 -0
- package/dist/schema/ref.d.ts.map +1 -1
- package/dist/schema/ref.js +18 -0
- package/dist/schema/ref.js.map +1 -1
- package/dist/schema/refine.d.ts +58 -9
- package/dist/schema/refine.d.ts.map +1 -1
- package/dist/schema/refine.js.map +1 -1
- package/dist/schema/regexp.d.ts +45 -0
- package/dist/schema/regexp.d.ts.map +1 -1
- package/dist/schema/regexp.js +46 -1
- package/dist/schema/regexp.js.map +1 -1
- package/dist/schema/string.d.ts +72 -6
- package/dist/schema/string.d.ts.map +1 -1
- package/dist/schema/string.js +56 -8
- package/dist/schema/string.js.map +1 -1
- package/dist/schema/subscription.d.ts +72 -2
- package/dist/schema/subscription.d.ts.map +1 -1
- package/dist/schema/subscription.js +59 -0
- package/dist/schema/subscription.js.map +1 -1
- package/dist/schema/token.d.ts +48 -0
- package/dist/schema/token.d.ts.map +1 -1
- package/dist/schema/token.js +49 -1
- package/dist/schema/token.js.map +1 -1
- package/dist/schema/typed-object.d.ts +73 -23
- package/dist/schema/typed-object.d.ts.map +1 -1
- package/dist/schema/typed-object.js +20 -1
- package/dist/schema/typed-object.js.map +1 -1
- package/dist/schema/typed-ref.d.ts +54 -0
- package/dist/schema/typed-ref.d.ts.map +1 -1
- package/dist/schema/typed-ref.js +16 -0
- package/dist/schema/typed-ref.js.map +1 -1
- package/dist/schema/typed-union.d.ts +51 -1
- package/dist/schema/typed-union.d.ts.map +1 -1
- package/dist/schema/typed-union.js +52 -2
- package/dist/schema/typed-union.js.map +1 -1
- package/dist/schema/union.d.ts +46 -0
- package/dist/schema/union.d.ts.map +1 -1
- package/dist/schema/union.js +41 -0
- package/dist/schema/union.js.map +1 -1
- package/dist/schema/unknown.d.ts +34 -0
- package/dist/schema/unknown.d.ts.map +1 -1
- package/dist/schema/unknown.js +34 -0
- package/dist/schema/unknown.js.map +1 -1
- package/dist/schema/with-default.d.ts +45 -0
- package/dist/schema/with-default.d.ts.map +1 -1
- package/dist/schema/with-default.js +45 -0
- package/dist/schema/with-default.js.map +1 -1
- package/dist/schema.d.ts +2 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +2 -1
- package/dist/schema.js.map +1 -1
- package/dist/util/if-any.d.ts +2 -0
- package/dist/util/if-any.d.ts.map +1 -0
- package/dist/util/if-any.js +3 -0
- package/dist/util/if-any.js.map +1 -0
- package/package.json +3 -3
- package/src/core/$type.ts +150 -18
- package/src/core/record-key.ts +44 -0
- package/src/core/result.ts +86 -4
- package/src/core/schema.ts +244 -9
- package/src/core/string-format.ts +259 -13
- package/src/core/types.ts +91 -3
- package/src/core/validation-error.ts +60 -0
- package/src/core/validation-issue.ts +68 -2
- package/src/core/validator.ts +373 -12
- package/src/helpers.test.ts +110 -29
- package/src/helpers.ts +54 -25
- package/src/schema/array.test.ts +94 -79
- package/src/schema/array.ts +48 -1
- package/src/schema/blob.ts +50 -1
- package/src/schema/boolean.ts +31 -1
- package/src/schema/bytes.ts +41 -1
- package/src/schema/cid.ts +41 -1
- package/src/schema/custom.ts +68 -1
- package/src/schema/dict.ts +47 -1
- package/src/schema/discriminated-union.ts +61 -1
- package/src/schema/enum.ts +50 -0
- package/src/schema/integer.ts +45 -1
- package/src/schema/intersection.ts +56 -0
- package/src/schema/{unknown-object.test.ts → lex-map.test.ts} +9 -9
- package/src/schema/lex-map.ts +63 -0
- package/src/schema/lex-value.test.ts +81 -0
- package/src/schema/lex-value.ts +86 -0
- package/src/schema/literal.ts +46 -0
- package/src/schema/never.ts +45 -1
- package/src/schema/null.ts +32 -1
- package/src/schema/nullable.ts +43 -0
- package/src/schema/object.ts +59 -1
- package/src/schema/optional.ts +44 -0
- package/src/schema/params.test.ts +133 -38
- package/src/schema/params.ts +237 -37
- package/src/schema/payload.test.ts +3 -3
- package/src/schema/payload.ts +145 -42
- package/src/schema/permission-set.ts +58 -0
- package/src/schema/permission.ts +42 -0
- package/src/schema/procedure.ts +64 -0
- package/src/schema/query.ts +55 -0
- package/src/schema/record.ts +82 -16
- package/src/schema/ref.ts +52 -0
- package/src/schema/refine.ts +58 -9
- package/src/schema/regexp.ts +47 -1
- package/src/schema/string.test.ts +99 -2
- package/src/schema/string.ts +108 -15
- package/src/schema/subscription.ts +72 -2
- package/src/schema/token.ts +50 -1
- package/src/schema/typed-object.ts +81 -16
- package/src/schema/typed-ref.ts +55 -0
- package/src/schema/typed-union.ts +58 -3
- package/src/schema/union.ts +47 -0
- package/src/schema/unknown.ts +35 -0
- package/src/schema/with-default.ts +46 -0
- package/src/schema.ts +2 -1
- package/src/util/if-any.ts +3 -0
- package/dist/schema/unknown-object.d.ts +0 -8
- package/dist/schema/unknown-object.d.ts.map +0 -1
- package/dist/schema/unknown-object.js +0 -19
- package/dist/schema/unknown-object.js.map +0 -1
- package/src/schema/unknown-object.ts +0 -19
package/src/schema/custom.ts
CHANGED
|
@@ -6,18 +6,48 @@ import {
|
|
|
6
6
|
ValidationContext,
|
|
7
7
|
} from '../core.js'
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Context object provided to custom assertion functions.
|
|
11
|
+
*
|
|
12
|
+
* @property path - Current validation path as an array of property keys
|
|
13
|
+
* @property addIssue - Function to add additional validation issues
|
|
14
|
+
*/
|
|
9
15
|
export type CustomAssertionContext = {
|
|
10
16
|
path: PropertyKey[]
|
|
11
17
|
addIssue(issue: Issue): void
|
|
12
18
|
}
|
|
13
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Type guard function for custom schema validation.
|
|
22
|
+
*
|
|
23
|
+
* @template TValue - The type to validate/narrow to
|
|
24
|
+
*/
|
|
14
25
|
export type CustomAssertion<TValue> = (
|
|
15
26
|
this: null,
|
|
16
27
|
input: unknown,
|
|
17
28
|
ctx: CustomAssertionContext,
|
|
18
29
|
) => input is TValue
|
|
19
30
|
|
|
20
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Schema with a custom validation function.
|
|
33
|
+
*
|
|
34
|
+
* Allows defining completely custom validation logic using a type guard
|
|
35
|
+
* assertion function. The function receives the input and validation context,
|
|
36
|
+
* and must return whether the input is valid.
|
|
37
|
+
*
|
|
38
|
+
* @template TValue - The validated output type
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* const schema = new CustomSchema(
|
|
43
|
+
* (input): input is Date => input instanceof Date,
|
|
44
|
+
* 'Expected a Date instance'
|
|
45
|
+
* )
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export class CustomSchema<out TValue = unknown> extends Schema<TValue> {
|
|
49
|
+
readonly type = 'custom' as const
|
|
50
|
+
|
|
21
51
|
constructor(
|
|
22
52
|
private readonly assertion: CustomAssertion<TValue>,
|
|
23
53
|
private readonly message: string,
|
|
@@ -35,6 +65,43 @@ export class CustomSchema<const TValue = unknown> extends Schema<TValue> {
|
|
|
35
65
|
}
|
|
36
66
|
}
|
|
37
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Creates a custom schema with a user-defined validation function.
|
|
70
|
+
*
|
|
71
|
+
* Use this when the built-in schemas don't cover your validation needs.
|
|
72
|
+
* The assertion function must be a type guard that narrows the input type.
|
|
73
|
+
*
|
|
74
|
+
* @param assertion - Type guard function that validates the input
|
|
75
|
+
* @param message - Error message when validation fails
|
|
76
|
+
* @param path - Optional path to associate with validation errors
|
|
77
|
+
* @returns A new {@link CustomSchema} instance
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* // Validate Date instances
|
|
82
|
+
* const dateSchema = l.custom(
|
|
83
|
+
* (input): input is Date => input instanceof Date && !isNaN(input.getTime()),
|
|
84
|
+
* 'Expected a valid Date'
|
|
85
|
+
* )
|
|
86
|
+
*
|
|
87
|
+
* // Validate specific object shape
|
|
88
|
+
* const pointSchema = l.custom(
|
|
89
|
+
* (input): input is { x: number; y: number } =>
|
|
90
|
+
* typeof input === 'object' &&
|
|
91
|
+
* input !== null &&
|
|
92
|
+
* typeof (input as any).x === 'number' &&
|
|
93
|
+
* typeof (input as any).y === 'number',
|
|
94
|
+
* 'Expected a point with x and y coordinates'
|
|
95
|
+
* )
|
|
96
|
+
*
|
|
97
|
+
* // With custom path
|
|
98
|
+
* const validConfig = l.custom(
|
|
99
|
+
* (input): input is Config => validateConfig(input),
|
|
100
|
+
* 'Invalid configuration',
|
|
101
|
+
* ['config']
|
|
102
|
+
* )
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
38
105
|
/*@__NO_SIDE_EFFECTS__*/
|
|
39
106
|
export function custom<TValue>(
|
|
40
107
|
assertion: CustomAssertion<TValue>,
|
package/src/schema/dict.ts
CHANGED
|
@@ -8,9 +8,24 @@ import {
|
|
|
8
8
|
} from '../core.js'
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
+
* Schema for validating dictionary/map-like objects with dynamic keys.
|
|
12
|
+
*
|
|
13
|
+
* Unlike `ObjectSchema` which validates a fixed set of properties, `DictSchema`
|
|
14
|
+
* validates objects where any string key is allowed, with both keys and values
|
|
15
|
+
* validated against their respective schemas.
|
|
16
|
+
*
|
|
11
17
|
* @note There is no dictionary in Lexicon schemas. This is a custom extension
|
|
12
18
|
* to allow map-like objects when using the lex library programmatically (i.e.
|
|
13
19
|
* not code generated from a lexicon schema).
|
|
20
|
+
*
|
|
21
|
+
* @template TKey - The validator type for dictionary keys (must validate strings)
|
|
22
|
+
* @template TValue - The validator type for dictionary values
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* const schema = new DictSchema(l.string(), l.integer())
|
|
27
|
+
* const result = schema.validate({ a: 1, b: 2, c: 3 })
|
|
28
|
+
* ```
|
|
14
29
|
*/
|
|
15
30
|
export class DictSchema<
|
|
16
31
|
const TKey extends Validator<string> = any,
|
|
@@ -19,6 +34,8 @@ export class DictSchema<
|
|
|
19
34
|
Record<InferInput<TKey>, InferInput<TValue>>,
|
|
20
35
|
Record<InferInput<TKey>, InferOutput<TValue>>
|
|
21
36
|
> {
|
|
37
|
+
readonly type = 'dict' as const
|
|
38
|
+
|
|
22
39
|
constructor(
|
|
23
40
|
readonly keySchema: TKey,
|
|
24
41
|
readonly valueSchema: TValue,
|
|
@@ -32,7 +49,7 @@ export class DictSchema<
|
|
|
32
49
|
options?: { ignoredKeys?: { has(k: string): boolean } },
|
|
33
50
|
) {
|
|
34
51
|
if (!isPlainObject(input)) {
|
|
35
|
-
return ctx.
|
|
52
|
+
return ctx.issueUnexpectedType(input, 'dict')
|
|
36
53
|
}
|
|
37
54
|
|
|
38
55
|
let copy: undefined | Record<string, unknown>
|
|
@@ -67,6 +84,35 @@ export class DictSchema<
|
|
|
67
84
|
}
|
|
68
85
|
}
|
|
69
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Creates a dictionary schema for validating map-like objects.
|
|
89
|
+
*
|
|
90
|
+
* Validates objects where all keys match the key schema and all values
|
|
91
|
+
* match the value schema. Useful for dynamic key-value mappings.
|
|
92
|
+
*
|
|
93
|
+
* @param key - Schema to validate each key (must be a string validator)
|
|
94
|
+
* @param value - Schema to validate each value
|
|
95
|
+
* @returns A new {@link DictSchema} instance
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* // String to number mapping
|
|
100
|
+
* const scoresSchema = l.dict(l.string(), l.integer())
|
|
101
|
+
* scoresSchema.parse({ alice: 100, bob: 85 })
|
|
102
|
+
*
|
|
103
|
+
* // Constrained keys
|
|
104
|
+
* const langSchema = l.dict(
|
|
105
|
+
* l.string({ minLength: 2, maxLength: 5 }), // Language codes
|
|
106
|
+
* l.string() // Translations
|
|
107
|
+
* )
|
|
108
|
+
*
|
|
109
|
+
* // Complex values
|
|
110
|
+
* const usersById = l.dict(
|
|
111
|
+
* l.string({ format: 'did' }),
|
|
112
|
+
* l.object({ name: l.string(), age: l.integer() })
|
|
113
|
+
* )
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
70
116
|
/*@__NO_SIDE_EFFECTS__*/
|
|
71
117
|
export function dict<
|
|
72
118
|
const TKey extends Validator<string>,
|
|
@@ -11,9 +11,22 @@ import { EnumSchema } from './enum.js'
|
|
|
11
11
|
import { LiteralSchema } from './literal.js'
|
|
12
12
|
import { ObjectSchema } from './object.js'
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Type representing a single variant in a discriminated union.
|
|
16
|
+
*
|
|
17
|
+
* Must be an ObjectSchema with the discriminator property using either
|
|
18
|
+
* a LiteralSchema or EnumSchema.
|
|
19
|
+
*
|
|
20
|
+
* @template Discriminator - The discriminator property name
|
|
21
|
+
*/
|
|
14
22
|
export type DiscriminatedUnionVariant<Discriminator extends string = string> =
|
|
15
23
|
ObjectSchema<Record<Discriminator, EnumSchema<any> | LiteralSchema<any>>>
|
|
16
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Type representing a non-empty tuple of discriminated union variants.
|
|
27
|
+
*
|
|
28
|
+
* @template TDiscriminator - The discriminator property name
|
|
29
|
+
*/
|
|
17
30
|
export type DiscriminatedUnionVariants<TDiscriminator extends string> =
|
|
18
31
|
readonly [
|
|
19
32
|
DiscriminatedUnionVariant<TDiscriminator>,
|
|
@@ -37,9 +50,26 @@ type DiscriminatedUnionSchemaOutput<TVariants extends readonly Validator[]> =
|
|
|
37
50
|
: never
|
|
38
51
|
|
|
39
52
|
/**
|
|
53
|
+
* Schema for validating discriminated unions of objects.
|
|
54
|
+
*
|
|
55
|
+
* More efficient than regular union schemas when discriminating on a known
|
|
56
|
+
* property. Looks up the correct variant schema directly based on the
|
|
57
|
+
* discriminator value instead of trying each variant in sequence.
|
|
58
|
+
*
|
|
40
59
|
* @note There is no discriminated union in Lexicon schemas. This is a custom
|
|
41
60
|
* extension to allow optimized validation of union of objects when using the
|
|
42
61
|
* lex library programmatically (i.e. not code generated from a lexicon schema).
|
|
62
|
+
*
|
|
63
|
+
* @template TDiscriminator - The discriminator property name
|
|
64
|
+
* @template TVariants - Tuple type of the variant schemas
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* const schema = new DiscriminatedUnionSchema('type', [
|
|
69
|
+
* l.object({ type: l.literal('text'), content: l.string() }),
|
|
70
|
+
* l.object({ type: l.literal('image'), url: l.string() }),
|
|
71
|
+
* ])
|
|
72
|
+
* ```
|
|
43
73
|
*/
|
|
44
74
|
export class DiscriminatedUnionSchema<
|
|
45
75
|
const TDiscriminator extends string,
|
|
@@ -48,6 +78,8 @@ export class DiscriminatedUnionSchema<
|
|
|
48
78
|
DiscriminatedUnionSchemaInput<TVariants>,
|
|
49
79
|
DiscriminatedUnionSchemaOutput<TVariants>
|
|
50
80
|
> {
|
|
81
|
+
readonly type = 'discriminatedUnion' as const
|
|
82
|
+
|
|
51
83
|
readonly variantsMap: Map<unknown, DiscriminatedUnionVariant<TDiscriminator>>
|
|
52
84
|
|
|
53
85
|
constructor(
|
|
@@ -64,7 +96,7 @@ export class DiscriminatedUnionSchema<
|
|
|
64
96
|
|
|
65
97
|
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
66
98
|
if (!isPlainObject(input)) {
|
|
67
|
-
return ctx.
|
|
99
|
+
return ctx.issueUnexpectedType(input, 'object')
|
|
68
100
|
}
|
|
69
101
|
|
|
70
102
|
const { discriminator } = this
|
|
@@ -124,6 +156,34 @@ function buildVariantsMap<Discriminator extends string>(
|
|
|
124
156
|
return variantsMap
|
|
125
157
|
}
|
|
126
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Creates a discriminated union schema for efficient object type switching.
|
|
161
|
+
*
|
|
162
|
+
* Unlike regular `union()`, this schema uses a discriminator property to
|
|
163
|
+
* directly look up the correct variant, providing O(1) validation instead
|
|
164
|
+
* of trying each variant sequentially.
|
|
165
|
+
*
|
|
166
|
+
* @param discriminator - Property name to discriminate on
|
|
167
|
+
* @param variants - Non-empty array of object schemas with the discriminator property
|
|
168
|
+
* @returns A new {@link DiscriminatedUnionSchema} instance
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```ts
|
|
172
|
+
* // Message types discriminated by 'kind'
|
|
173
|
+
* const messageSchema = l.discriminatedUnion('kind', [
|
|
174
|
+
* l.object({ kind: l.literal('text'), text: l.string() }),
|
|
175
|
+
* l.object({ kind: l.literal('image'), url: l.string(), alt: l.optional(l.string()) }),
|
|
176
|
+
* l.object({ kind: l.literal('video'), url: l.string(), duration: l.integer() }),
|
|
177
|
+
* ])
|
|
178
|
+
*
|
|
179
|
+
* // Using enums for multiple values mapping to same variant
|
|
180
|
+
* const statusSchema = l.discriminatedUnion('status', [
|
|
181
|
+
* l.object({ status: l.enum(['pending', 'processing']), startedAt: l.string() }),
|
|
182
|
+
* l.object({ status: l.literal('completed'), completedAt: l.string() }),
|
|
183
|
+
* l.object({ status: l.literal('failed'), error: l.string() }),
|
|
184
|
+
* ])
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
127
187
|
/*@__NO_SIDE_EFFECTS__*/
|
|
128
188
|
export function discriminatedUnion<
|
|
129
189
|
const Discriminator extends string,
|
package/src/schema/enum.ts
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
import { Schema, ValidationContext } from '../core.js'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Schema that accepts one of several specific literal values.
|
|
5
|
+
*
|
|
6
|
+
* Validates that the input matches one of the allowed values using strict
|
|
7
|
+
* equality. Similar to TypeScript union of literals.
|
|
8
|
+
*
|
|
9
|
+
* @template TValue - The union of literal types
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const schema = new EnumSchema(['pending', 'active', 'completed'])
|
|
14
|
+
* schema.validate('active') // success
|
|
15
|
+
* schema.validate('invalid') // fails
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
3
18
|
export class EnumSchema<
|
|
4
19
|
const TValue extends null | string | number | boolean,
|
|
5
20
|
> extends Schema<TValue> {
|
|
21
|
+
readonly type = 'enum' as const
|
|
22
|
+
|
|
6
23
|
constructor(readonly values: readonly TValue[]) {
|
|
7
24
|
super()
|
|
8
25
|
}
|
|
@@ -16,6 +33,39 @@ export class EnumSchema<
|
|
|
16
33
|
}
|
|
17
34
|
}
|
|
18
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Creates an enum schema that accepts one of the specified values.
|
|
38
|
+
*
|
|
39
|
+
* Similar to TypeScript's union of string literals. Use `l.enum()` for
|
|
40
|
+
* the namespace-friendly alias.
|
|
41
|
+
*
|
|
42
|
+
* @param value - Array of allowed values
|
|
43
|
+
* @returns A new {@link EnumSchema} instance
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* // String enum
|
|
48
|
+
* const statusSchema = l.enum(['pending', 'active', 'completed', 'failed'])
|
|
49
|
+
*
|
|
50
|
+
* // Number enum
|
|
51
|
+
* const prioritySchema = l.enum([1, 2, 3, 4, 5])
|
|
52
|
+
*
|
|
53
|
+
* // Mixed types
|
|
54
|
+
* const mixedSchema = l.enum(['auto', 0, 1, true])
|
|
55
|
+
*
|
|
56
|
+
* // Use in objects
|
|
57
|
+
* const taskSchema = l.object({
|
|
58
|
+
* title: l.string(),
|
|
59
|
+
* status: l.enum(['todo', 'in-progress', 'done']),
|
|
60
|
+
* })
|
|
61
|
+
*
|
|
62
|
+
* // In discriminated unions
|
|
63
|
+
* const resultSchema = l.discriminatedUnion('status', [
|
|
64
|
+
* l.object({ status: l.enum(['pending', 'processing']), progress: l.integer() }),
|
|
65
|
+
* l.object({ status: l.literal('completed'), result: l.unknown() }),
|
|
66
|
+
* ])
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
19
69
|
/*@__NO_SIDE_EFFECTS__*/
|
|
20
70
|
export function enumSchema<const V extends null | string | number | boolean>(
|
|
21
71
|
value: readonly V[],
|
package/src/schema/integer.ts
CHANGED
|
@@ -1,19 +1,39 @@
|
|
|
1
1
|
import { Schema, ValidationContext } from '../core.js'
|
|
2
2
|
import { memoizedOptions } from '../util/memoize.js'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Configuration options for integer schema validation.
|
|
6
|
+
*
|
|
7
|
+
* @property minimum - Minimum allowed value (inclusive)
|
|
8
|
+
* @property maximum - Maximum allowed value (inclusive)
|
|
9
|
+
*/
|
|
4
10
|
export type IntegerSchemaOptions = {
|
|
5
11
|
minimum?: number
|
|
6
12
|
maximum?: number
|
|
7
13
|
}
|
|
8
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Schema for validating integer values with optional range constraints.
|
|
17
|
+
*
|
|
18
|
+
* Only accepts safe integers (values that can be exactly represented in JavaScript).
|
|
19
|
+
* Use {@link IntegerSchemaOptions} to constrain the allowed range.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* const schema = new IntegerSchema({ minimum: 0, maximum: 100 })
|
|
24
|
+
* const result = schema.validate(42)
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
9
27
|
export class IntegerSchema extends Schema<number> {
|
|
28
|
+
readonly type = 'integer' as const
|
|
29
|
+
|
|
10
30
|
constructor(readonly options?: IntegerSchemaOptions) {
|
|
11
31
|
super()
|
|
12
32
|
}
|
|
13
33
|
|
|
14
34
|
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
15
35
|
if (!isInteger(input)) {
|
|
16
|
-
return ctx.
|
|
36
|
+
return ctx.issueUnexpectedType(input, 'integer')
|
|
17
37
|
}
|
|
18
38
|
|
|
19
39
|
if (this.options?.minimum != null && input < this.options.minimum) {
|
|
@@ -35,6 +55,30 @@ function isInteger(input: unknown): input is number {
|
|
|
35
55
|
return Number.isSafeInteger(input)
|
|
36
56
|
}
|
|
37
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Creates an integer schema with optional minimum and maximum constraints.
|
|
60
|
+
*
|
|
61
|
+
* Validates that the input is a safe integer (can be exactly represented in JavaScript)
|
|
62
|
+
* and optionally falls within a specified range.
|
|
63
|
+
*
|
|
64
|
+
* @param options - Optional configuration for minimum and maximum values
|
|
65
|
+
* @returns A new {@link IntegerSchema} instance
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* // Basic integer
|
|
70
|
+
* const countSchema = l.integer()
|
|
71
|
+
*
|
|
72
|
+
* // With minimum value
|
|
73
|
+
* const positiveSchema = l.integer({ minimum: 1 })
|
|
74
|
+
*
|
|
75
|
+
* // With range constraints
|
|
76
|
+
* const percentSchema = l.integer({ minimum: 0, maximum: 100 })
|
|
77
|
+
*
|
|
78
|
+
* // Age validation
|
|
79
|
+
* const ageSchema = l.integer({ minimum: 0, maximum: 150 })
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
38
82
|
export const integer = /*#__PURE__*/ memoizedOptions(function (
|
|
39
83
|
options?: IntegerSchemaOptions,
|
|
40
84
|
) {
|
|
@@ -9,10 +9,15 @@ import { DictSchema } from './dict.js'
|
|
|
9
9
|
import { ObjectSchema } from './object.js'
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
+
* Type utility for computing the intersection of two object types.
|
|
13
|
+
*
|
|
12
14
|
* Allows to more accurately represent the intersection of two object types
|
|
13
15
|
* where both types may share some keys, and one of them uses an index
|
|
14
16
|
* signature.
|
|
15
17
|
*
|
|
18
|
+
* @template A - First object type (typically from ObjectSchema)
|
|
19
|
+
* @template B - Second object type (typically from DictSchema)
|
|
20
|
+
*
|
|
16
21
|
* @see {@link https://www.typescriptlang.org/play/?#code/C4TwDgpgBAglC8UDeUBmB7dAuKByARgIYBOuUAvlAGTJQDaA+lAJYB2UAzsMWwOYC6OVgFcAtvgjEKAKGkATCAGMANiWiL0rLlEI4YsjVuBQA1hBA4uPVrwRQARBnT2Dm7QDdCy4dESE6ZiD8UAD0IVAi4pJQABQcABbowspyUBIORMT2AJSyEAAeYOjExqCQUACSrMCSHErAzJoAPNJQsFAFNaxyHFAASkrFck1WfAA0UMKsJqzoAO6sAHxjrVAAQh35XT39g8TDozYTUzPzSyuLdqtwVKttMYHoqO00j88bnRDdvawQ7pJ3NpQAD860BbRwSHBQLadAA0ix2G91oJ1vDggAfWABcxPF5QOH8aFtci5aRlaAwVDMfIQVKIKo1Yh1RQNZq0Jw4AgkMjkCYoRiIzjcPioyISKTkRayBQqNRQQzaQgAMRpdL01NpclcRignm8EFVWrsKrVchxQVC4XF0SxmSAA Playground link}
|
|
17
22
|
*/
|
|
18
23
|
export type Intersect<A, B> = B[keyof B] extends never
|
|
@@ -24,6 +29,26 @@ export type Intersect<A, B> = B[keyof B] extends never
|
|
|
24
29
|
// index signature could return a value from either A or B
|
|
25
30
|
A & { [K in keyof B]: B[K] | A[keyof A & K] }
|
|
26
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Schema for combining an object schema with a dictionary schema.
|
|
34
|
+
*
|
|
35
|
+
* Validates that the input matches both the fixed object shape and allows
|
|
36
|
+
* additional properties that match the dictionary schema. Properties defined
|
|
37
|
+
* in the object schema are validated by the object, and remaining properties
|
|
38
|
+
* are validated by the dictionary.
|
|
39
|
+
*
|
|
40
|
+
* @template Left - The ObjectSchema type for fixed properties
|
|
41
|
+
* @template Right - The DictSchema type for additional properties
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* const schema = new IntersectionSchema(
|
|
46
|
+
* l.object({ name: l.string() }),
|
|
47
|
+
* l.dict(l.string(), l.integer())
|
|
48
|
+
* )
|
|
49
|
+
* // Validates: { name: 'test', score: 100, count: 5 }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
27
52
|
export class IntersectionSchema<
|
|
28
53
|
const Left extends ObjectSchema = any,
|
|
29
54
|
const Right extends DictSchema = any,
|
|
@@ -31,6 +56,8 @@ export class IntersectionSchema<
|
|
|
31
56
|
Simplify<Intersect<InferInput<Left>, InferInput<Right>>>,
|
|
32
57
|
Simplify<Intersect<InferOutput<Left>, InferOutput<Right>>>
|
|
33
58
|
> {
|
|
59
|
+
readonly type = 'intersection' as const
|
|
60
|
+
|
|
34
61
|
constructor(
|
|
35
62
|
protected readonly left: Left,
|
|
36
63
|
protected readonly right: Right,
|
|
@@ -48,6 +75,35 @@ export class IntersectionSchema<
|
|
|
48
75
|
}
|
|
49
76
|
}
|
|
50
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Creates an intersection schema combining fixed object properties with dynamic dictionary properties.
|
|
80
|
+
*
|
|
81
|
+
* Useful for objects that have a known set of properties plus additional
|
|
82
|
+
* arbitrary properties that follow a pattern.
|
|
83
|
+
*
|
|
84
|
+
* @param left - Object schema defining the fixed, known properties
|
|
85
|
+
* @param right - Dictionary schema for validating additional properties
|
|
86
|
+
* @returns A new {@link IntersectionSchema} instance
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* // Object with fixed and dynamic properties
|
|
91
|
+
* const configSchema = l.intersection(
|
|
92
|
+
* l.object({
|
|
93
|
+
* version: l.integer(),
|
|
94
|
+
* name: l.string(),
|
|
95
|
+
* }),
|
|
96
|
+
* l.dict(l.string(), l.string()) // Additional string properties
|
|
97
|
+
* )
|
|
98
|
+
*
|
|
99
|
+
* configSchema.parse({
|
|
100
|
+
* version: 1,
|
|
101
|
+
* name: 'my-config',
|
|
102
|
+
* customField: 'value',
|
|
103
|
+
* anotherField: 'another',
|
|
104
|
+
* })
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
51
107
|
/*@__NO_SIDE_EFFECTS__*/
|
|
52
108
|
export function intersection<
|
|
53
109
|
const Left extends ObjectSchema,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import {
|
|
2
|
+
import { lexMap } from './lex-map.js'
|
|
3
3
|
|
|
4
|
-
describe(
|
|
4
|
+
describe(lexMap, () => {
|
|
5
5
|
describe('basic validation', () => {
|
|
6
|
-
const schema =
|
|
6
|
+
const schema = lexMap()
|
|
7
7
|
|
|
8
8
|
it('accepts empty plain objects', () => {
|
|
9
9
|
const result = schema.safeParse({})
|
|
@@ -106,7 +106,7 @@ describe('UnknownObjectSchema', () => {
|
|
|
106
106
|
})
|
|
107
107
|
|
|
108
108
|
describe('rejects non-plain-objects', () => {
|
|
109
|
-
const schema =
|
|
109
|
+
const schema = lexMap()
|
|
110
110
|
|
|
111
111
|
it('rejects strings', () => {
|
|
112
112
|
const result = schema.safeParse('not an object')
|
|
@@ -208,7 +208,7 @@ describe('UnknownObjectSchema', () => {
|
|
|
208
208
|
})
|
|
209
209
|
|
|
210
210
|
describe('rejects invalid value types', () => {
|
|
211
|
-
const schema =
|
|
211
|
+
const schema = lexMap()
|
|
212
212
|
|
|
213
213
|
it('rejects objects with floating point numbers', () => {
|
|
214
214
|
const result = schema.safeParse({ value: 3.14 })
|
|
@@ -288,7 +288,7 @@ describe('UnknownObjectSchema', () => {
|
|
|
288
288
|
})
|
|
289
289
|
|
|
290
290
|
describe('rejects invalid nested values', () => {
|
|
291
|
-
const schema =
|
|
291
|
+
const schema = lexMap()
|
|
292
292
|
|
|
293
293
|
it('rejects deeply nested invalid values', () => {
|
|
294
294
|
const result = schema.safeParse({
|
|
@@ -335,7 +335,7 @@ describe('UnknownObjectSchema', () => {
|
|
|
335
335
|
})
|
|
336
336
|
|
|
337
337
|
describe('edge cases', () => {
|
|
338
|
-
const schema =
|
|
338
|
+
const schema = lexMap()
|
|
339
339
|
|
|
340
340
|
it('accepts objects with numeric string keys', () => {
|
|
341
341
|
const obj = { '0': 'zero', '1': 'one', '2': 'two' }
|
|
@@ -500,7 +500,7 @@ describe('UnknownObjectSchema', () => {
|
|
|
500
500
|
})
|
|
501
501
|
|
|
502
502
|
describe('large objects', () => {
|
|
503
|
-
const schema =
|
|
503
|
+
const schema = lexMap()
|
|
504
504
|
|
|
505
505
|
it('accepts objects with many keys', () => {
|
|
506
506
|
const obj: Record<string, number> = {}
|
|
@@ -538,7 +538,7 @@ describe('UnknownObjectSchema', () => {
|
|
|
538
538
|
})
|
|
539
539
|
|
|
540
540
|
describe('preservation of input', () => {
|
|
541
|
-
const schema =
|
|
541
|
+
const schema = lexMap()
|
|
542
542
|
|
|
543
543
|
it('preserves the original object reference', () => {
|
|
544
544
|
const input = { key: 'value', count: 42 }
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { LexMap, isPlainObject } from '@atproto/lex-data'
|
|
2
|
+
import { Schema, ValidationContext } from '../core.js'
|
|
3
|
+
import { memoizedOptions } from '../util/memoize.js'
|
|
4
|
+
import { lexValue } from './lex-value.js'
|
|
5
|
+
|
|
6
|
+
const propertyValueSchema = /*#__PURE__*/ lexValue()
|
|
7
|
+
|
|
8
|
+
export type { LexMap }
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* AT Protocol lexicon schema definitions with "type": "unknown" are represented
|
|
12
|
+
* as plain objects with string keys and values that are valid AT Protocol data
|
|
13
|
+
* types (string, integer, boolean, null, bytes, cid, array, or object). This
|
|
14
|
+
* type alias corresponds to the expected structure of such "unknown" schema
|
|
15
|
+
* values.
|
|
16
|
+
*/
|
|
17
|
+
export class LexMapSchema extends Schema<LexMap> {
|
|
18
|
+
readonly type = 'lexMap' as const
|
|
19
|
+
|
|
20
|
+
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
21
|
+
if (!isPlainObject(input)) {
|
|
22
|
+
return ctx.issueUnexpectedType(input, 'object')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
for (const key of Object.keys(input)) {
|
|
26
|
+
// @NOTE We use a lexValue() schema here to recursively validate all
|
|
27
|
+
// nested values, which ensures that the error reporting includes the
|
|
28
|
+
// correct path and type information for any invalid nested values. This
|
|
29
|
+
// allows for more informative error descriptions than a simple "isLexMap"
|
|
30
|
+
// check.
|
|
31
|
+
const r = ctx.validateChild(input, key, propertyValueSchema) // recursively validate all properties
|
|
32
|
+
if (!r.success) return r
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return ctx.success(input)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates a schema that accepts any plain object with string keys and values
|
|
41
|
+
* that are valid AT Protocol data types (string, integer, boolean, null, bytes,
|
|
42
|
+
* cid, array, or object).
|
|
43
|
+
*
|
|
44
|
+
* @see {@link LexMap} from `@atproto/lex-data` for the type definition of valid AT Protocol data types
|
|
45
|
+
* @returns A new {@link LexMapSchema} instance
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* // Accept any object shape
|
|
50
|
+
* const schema = l.lexMap()
|
|
51
|
+
*
|
|
52
|
+
* schema.validate({ any: 'props' }) // success
|
|
53
|
+
* schema.validate([1, 2, 3]) // fails - only plain objects are accepted
|
|
54
|
+
* schema.validate({ foo: new Date() }) // fails - Date is not a valid LexValue
|
|
55
|
+
* schema.validate({ foo: 1.2 }) // fails - 1.2 is not a valid LexValue (not an integer)
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export const lexMap = /*#__PURE__*/ memoizedOptions(function () {
|
|
59
|
+
return new LexMapSchema()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
/** @deprecated Use {@link lexMap} instead */
|
|
63
|
+
export const unknownObject = lexMap
|