@atproto/lex-schema 0.0.2 → 0.0.3
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 +68 -0
- package/dist/core/$type.d.ts +6 -3
- package/dist/core/$type.d.ts.map +1 -1
- package/dist/core/$type.js +1 -0
- package/dist/core/$type.js.map +1 -1
- package/dist/core/record-key.d.ts +3 -3
- package/dist/core/record-key.d.ts.map +1 -1
- package/dist/core/record-key.js +12 -6
- package/dist/core/record-key.js.map +1 -1
- package/dist/core/result.d.ts.map +1 -1
- package/dist/core/result.js +6 -0
- package/dist/core/result.js.map +1 -1
- package/dist/core/string-format.d.ts +30 -27
- package/dist/core/string-format.d.ts.map +1 -1
- package/dist/core/string-format.js +56 -42
- package/dist/core/string-format.js.map +1 -1
- package/dist/core/types.d.ts +9 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/external.d.ts +31 -28
- package/dist/external.d.ts.map +1 -1
- package/dist/external.js +33 -17
- package/dist/external.js.map +1 -1
- package/dist/schema/_parameters.d.ts +2 -2
- package/dist/schema/_parameters.d.ts.map +1 -1
- package/dist/schema/array.d.ts +5 -6
- package/dist/schema/array.d.ts.map +1 -1
- package/dist/schema/array.js +5 -6
- package/dist/schema/array.js.map +1 -1
- package/dist/schema/blob.d.ts +2 -3
- package/dist/schema/blob.d.ts.map +1 -1
- package/dist/schema/blob.js +1 -2
- package/dist/schema/blob.js.map +1 -1
- package/dist/schema/boolean.d.ts +4 -5
- package/dist/schema/boolean.d.ts.map +1 -1
- package/dist/schema/boolean.js +2 -3
- package/dist/schema/boolean.js.map +1 -1
- package/dist/schema/bytes.d.ts +3 -4
- package/dist/schema/bytes.d.ts.map +1 -1
- package/dist/schema/bytes.js +2 -3
- package/dist/schema/bytes.js.map +1 -1
- package/dist/schema/cid.d.ts +13 -6
- package/dist/schema/cid.d.ts.map +1 -1
- package/dist/schema/cid.js +2 -4
- package/dist/schema/cid.js.map +1 -1
- package/dist/schema/custom.d.ts +3 -4
- package/dist/schema/custom.d.ts.map +1 -1
- package/dist/schema/custom.js +4 -3
- package/dist/schema/custom.js.map +1 -1
- package/dist/schema/dict.d.ts +3 -3
- package/dist/schema/dict.d.ts.map +1 -1
- package/dist/schema/dict.js +1 -1
- package/dist/schema/dict.js.map +1 -1
- package/dist/schema/discriminated-union.d.ts +15 -24
- package/dist/schema/discriminated-union.d.ts.map +1 -1
- package/dist/schema/discriminated-union.js +40 -64
- package/dist/schema/discriminated-union.js.map +1 -1
- package/dist/schema/enum.d.ts +8 -4
- package/dist/schema/enum.d.ts.map +1 -1
- package/dist/schema/enum.js +5 -3
- package/dist/schema/enum.js.map +1 -1
- package/dist/schema/integer.d.ts +3 -4
- package/dist/schema/integer.d.ts.map +1 -1
- package/dist/schema/integer.js +3 -4
- package/dist/schema/integer.js.map +1 -1
- package/dist/schema/intersection.d.ts +22 -14
- package/dist/schema/intersection.d.ts.map +1 -1
- package/dist/schema/intersection.js +12 -22
- package/dist/schema/intersection.js.map +1 -1
- package/dist/schema/literal.d.ts +7 -3
- package/dist/schema/literal.d.ts.map +1 -1
- package/dist/schema/literal.js +5 -3
- package/dist/schema/literal.js.map +1 -1
- package/dist/schema/never.d.ts +2 -2
- package/dist/schema/never.d.ts.map +1 -1
- package/dist/schema/never.js +1 -1
- package/dist/schema/never.js.map +1 -1
- package/dist/schema/null.d.ts +2 -3
- package/dist/schema/null.d.ts.map +1 -1
- package/dist/schema/null.js +1 -2
- package/dist/schema/null.js.map +1 -1
- package/dist/schema/nullable.d.ts +7 -0
- package/dist/schema/nullable.d.ts.map +1 -0
- package/dist/schema/nullable.js +19 -0
- package/dist/schema/nullable.js.map +1 -0
- package/dist/schema/object.d.ts +10 -44
- package/dist/schema/object.d.ts.map +1 -1
- package/dist/schema/object.js +10 -46
- package/dist/schema/object.js.map +1 -1
- package/dist/schema/optional.d.ts +7 -0
- package/dist/schema/optional.d.ts.map +1 -0
- package/dist/schema/optional.js +25 -0
- package/dist/schema/optional.js.map +1 -0
- package/dist/schema/params.d.ts +14 -19
- package/dist/schema/params.d.ts.map +1 -1
- package/dist/schema/params.js +10 -24
- package/dist/schema/params.js.map +1 -1
- package/dist/schema/payload.d.ts +4 -4
- package/dist/schema/payload.d.ts.map +1 -1
- package/dist/schema/payload.js.map +1 -1
- package/dist/schema/permission-set.d.ts +6 -6
- package/dist/schema/permission-set.d.ts.map +1 -1
- package/dist/schema/permission-set.js +1 -2
- package/dist/schema/permission-set.js.map +1 -1
- package/dist/schema/permission.d.ts +0 -1
- package/dist/schema/permission.d.ts.map +1 -1
- package/dist/schema/permission.js +0 -1
- package/dist/schema/permission.js.map +1 -1
- package/dist/schema/procedure.d.ts +8 -9
- package/dist/schema/procedure.d.ts.map +1 -1
- package/dist/schema/procedure.js +0 -1
- package/dist/schema/procedure.js.map +1 -1
- package/dist/schema/query.d.ts +7 -8
- package/dist/schema/query.d.ts.map +1 -1
- package/dist/schema/query.js +0 -1
- package/dist/schema/query.js.map +1 -1
- package/dist/schema/record.d.ts +34 -28
- package/dist/schema/record.d.ts.map +1 -1
- package/dist/schema/record.js +1 -2
- package/dist/schema/record.js.map +1 -1
- package/dist/schema/ref.d.ts +2 -3
- package/dist/schema/ref.d.ts.map +1 -1
- package/dist/schema/ref.js +1 -2
- package/dist/schema/ref.js.map +1 -1
- package/dist/schema/refine.d.ts +18 -0
- package/dist/schema/refine.d.ts.map +1 -0
- package/dist/schema/refine.js +33 -0
- package/dist/schema/refine.js.map +1 -0
- package/dist/schema/regexp.d.ts +7 -0
- package/dist/schema/regexp.d.ts.map +1 -0
- package/dist/schema/regexp.js +22 -0
- package/dist/schema/regexp.js.map +1 -0
- package/dist/schema/string.d.ts +4 -8
- package/dist/schema/string.d.ts.map +1 -1
- package/dist/schema/string.js +6 -3
- package/dist/schema/string.js.map +1 -1
- package/dist/schema/subscription.d.ts +7 -6
- package/dist/schema/subscription.d.ts.map +1 -1
- package/dist/schema/subscription.js.map +1 -1
- package/dist/schema/token.d.ts +2 -3
- package/dist/schema/token.d.ts.map +1 -1
- package/dist/schema/token.js +1 -2
- package/dist/schema/token.js.map +1 -1
- package/dist/schema/typed-object.d.ts +29 -27
- package/dist/schema/typed-object.d.ts.map +1 -1
- package/dist/schema/typed-object.js +1 -2
- package/dist/schema/typed-object.js.map +1 -1
- package/dist/schema/typed-ref.d.ts +2 -2
- package/dist/schema/typed-ref.d.ts.map +1 -1
- package/dist/schema/typed-ref.js +1 -1
- package/dist/schema/typed-ref.js.map +1 -1
- package/dist/schema/typed-union.d.ts +3 -4
- package/dist/schema/typed-union.d.ts.map +1 -1
- package/dist/schema/typed-union.js +3 -10
- package/dist/schema/typed-union.js.map +1 -1
- package/dist/schema/union.d.ts +2 -2
- package/dist/schema/union.d.ts.map +1 -1
- package/dist/schema/union.js +1 -1
- package/dist/schema/union.js.map +1 -1
- package/dist/schema/unknown-object.d.ts +2 -3
- package/dist/schema/unknown-object.d.ts.map +1 -1
- package/dist/schema/unknown-object.js +1 -2
- package/dist/schema/unknown-object.js.map +1 -1
- package/dist/schema/unknown.d.ts +2 -2
- package/dist/schema/unknown.d.ts.map +1 -1
- package/dist/schema/unknown.js +1 -1
- package/dist/schema/unknown.js.map +1 -1
- package/dist/schema.d.ts +4 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +6 -1
- package/dist/schema.js.map +1 -1
- package/dist/util/array-agg.d.ts.map +1 -1
- package/dist/util/array-agg.js +1 -0
- package/dist/util/array-agg.js.map +1 -1
- package/dist/util/lazy-property.d.ts +2 -0
- package/dist/util/lazy-property.d.ts.map +1 -0
- package/dist/util/lazy-property.js +14 -0
- package/dist/util/lazy-property.js.map +1 -0
- package/dist/validation/schema.d.ts +24 -0
- package/dist/validation/schema.d.ts.map +1 -0
- package/dist/validation/schema.js +57 -0
- package/dist/validation/schema.js.map +1 -0
- package/dist/validation/validation-error.d.ts +3 -3
- package/dist/validation/validation-error.d.ts.map +1 -1
- package/dist/validation/validation-error.js +32 -4
- package/dist/validation/validation-error.js.map +1 -1
- package/dist/validation/validation-issue.d.ts +32 -24
- package/dist/validation/validation-issue.d.ts.map +1 -1
- package/dist/validation/validation-issue.js +136 -92
- package/dist/validation/validation-issue.js.map +1 -1
- package/dist/validation/validator.d.ts +20 -50
- package/dist/validation/validator.d.ts.map +1 -1
- package/dist/validation/validator.js +40 -134
- package/dist/validation/validator.js.map +1 -1
- package/dist/validation.d.ts +1 -0
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +1 -0
- package/dist/validation.js.map +1 -1
- package/package.json +8 -4
- package/src/core/$type.ts +7 -4
- package/src/core/record-key.ts +12 -5
- package/src/core/result.ts +6 -0
- package/src/core/string-format.ts +97 -61
- package/src/core/types.ts +12 -6
- package/src/external.ts +92 -70
- package/src/schema/_parameters.test.ts +416 -0
- package/src/schema/array.test.ts +237 -0
- package/src/schema/array.ts +17 -11
- package/src/schema/blob.test.ts +506 -0
- package/src/schema/blob.ts +3 -5
- package/src/schema/boolean.test.ts +116 -0
- package/src/schema/boolean.ts +5 -7
- package/src/schema/bytes.test.ts +226 -0
- package/src/schema/bytes.ts +4 -6
- package/src/schema/cid.test.ts +155 -0
- package/src/schema/cid.ts +14 -8
- package/src/schema/custom.test.ts +413 -0
- package/src/schema/custom.ts +10 -8
- package/src/schema/dict.test.ts +198 -0
- package/src/schema/dict.ts +6 -8
- package/src/schema/discriminated-union.test.ts +675 -0
- package/src/schema/discriminated-union.ts +68 -95
- package/src/schema/enum.test.ts +396 -0
- package/src/schema/enum.ts +12 -5
- package/src/schema/integer.test.ts +312 -0
- package/src/schema/integer.ts +5 -7
- package/src/schema/intersection.test.ts +32 -0
- package/src/schema/intersection.ts +37 -40
- package/src/schema/literal.test.ts +531 -0
- package/src/schema/literal.ts +12 -5
- package/src/schema/never.test.ts +174 -0
- package/src/schema/never.ts +3 -10
- package/src/schema/null.test.ts +79 -0
- package/src/schema/null.ts +3 -5
- package/src/schema/nullable.test.ts +480 -0
- package/src/schema/nullable.ts +23 -0
- package/src/schema/object.test.ts +47 -115
- package/src/schema/object.ts +19 -123
- package/src/schema/optional.test.ts +485 -0
- package/src/schema/optional.ts +31 -0
- package/src/schema/params.test.ts +582 -0
- package/src/schema/params.ts +37 -55
- package/src/schema/payload.test.ts +345 -0
- package/src/schema/payload.ts +5 -5
- package/src/schema/permission-set.test.ts +679 -0
- package/src/schema/permission-set.ts +6 -8
- package/src/schema/permission.test.ts +536 -0
- package/src/schema/permission.ts +0 -2
- package/src/schema/procedure.test.ts +443 -0
- package/src/schema/procedure.ts +11 -13
- package/src/schema/query.test.ts +408 -0
- package/src/schema/query.ts +9 -11
- package/src/schema/record.test.ts +694 -0
- package/src/schema/record.ts +38 -36
- package/src/schema/ref.test.ts +365 -0
- package/src/schema/ref.ts +8 -5
- package/src/schema/refine.test.ts +578 -0
- package/src/schema/refine.ts +85 -0
- package/src/schema/regexp.test.ts +580 -0
- package/src/schema/regexp.ts +22 -0
- package/src/schema/string.test.ts +612 -0
- package/src/schema/string.ts +11 -17
- package/src/schema/subscription.test.ts +689 -0
- package/src/schema/subscription.ts +13 -8
- package/src/schema/token.test.ts +428 -0
- package/src/schema/token.ts +3 -5
- package/src/schema/typed-object.test.ts +612 -0
- package/src/schema/typed-object.ts +23 -20
- package/src/schema/typed-ref.test.ts +823 -0
- package/src/schema/typed-ref.ts +10 -5
- package/src/schema/typed-union.test.ts +378 -0
- package/src/schema/typed-union.ts +6 -15
- package/src/schema/union.test.ts +200 -0
- package/src/schema/union.ts +5 -4
- package/src/schema/unknown-object.test.ts +592 -0
- package/src/schema/unknown-object.ts +3 -5
- package/src/schema/unknown.test.ts +312 -0
- package/src/schema/unknown.ts +3 -3
- package/src/schema.ts +7 -1
- package/src/util/array-agg.ts +1 -0
- package/src/util/lazy-property.ts +14 -0
- package/src/validation/schema.ts +92 -0
- package/src/validation/validation-error.ts +60 -9
- package/src/validation/validation-issue.ts +141 -144
- package/src/validation/validator.ts +67 -206
- package/src/validation.ts +1 -0
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +7 -0
- package/tsconfig.tests.json +9 -0
package/src/schema/record.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LexiconRecordKey, NsidString, Simplify, TidString } from '../core.js'
|
|
2
2
|
import {
|
|
3
3
|
Infer,
|
|
4
|
+
Schema,
|
|
4
5
|
ValidationResult,
|
|
5
6
|
Validator,
|
|
6
7
|
ValidatorContext,
|
|
@@ -9,26 +10,24 @@ import { LiteralSchema } from './literal.js'
|
|
|
9
10
|
import { StringSchema } from './string.js'
|
|
10
11
|
|
|
11
12
|
export type InferRecordKey<R extends RecordSchema> =
|
|
12
|
-
R extends RecordSchema<infer K
|
|
13
|
-
? RecordKeySchemaOutput<K>
|
|
14
|
-
: never
|
|
13
|
+
R extends RecordSchema<infer K> ? RecordKeySchemaOutput<K> : never
|
|
15
14
|
|
|
16
|
-
export
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
Output extends Infer<Schema> & { $type: Type } = Infer<Schema> & {
|
|
21
|
-
$type: Type
|
|
22
|
-
},
|
|
23
|
-
> extends Validator<Output> {
|
|
24
|
-
readonly lexiconType = 'record' as const
|
|
15
|
+
export type RecordSchemaOutput<
|
|
16
|
+
T extends NsidString,
|
|
17
|
+
S extends Validator<{ [_ in string]?: unknown }>,
|
|
18
|
+
> = Simplify<Omit<Infer<S>, '$type'> & { $type: T }>
|
|
25
19
|
|
|
26
|
-
|
|
20
|
+
export class RecordSchema<
|
|
21
|
+
K extends LexiconRecordKey = any,
|
|
22
|
+
T extends NsidString = any,
|
|
23
|
+
S extends Validator<{ [_ in string]?: unknown }> = any,
|
|
24
|
+
> extends Schema<RecordSchemaOutput<T, S>> {
|
|
25
|
+
keySchema: RecordKeySchema<K>
|
|
27
26
|
|
|
28
27
|
constructor(
|
|
29
|
-
readonly key:
|
|
30
|
-
readonly $type:
|
|
31
|
-
readonly schema:
|
|
28
|
+
readonly key: K,
|
|
29
|
+
readonly $type: T,
|
|
30
|
+
readonly schema: S,
|
|
32
31
|
) {
|
|
33
32
|
super()
|
|
34
33
|
this.keySchema = recordKey(key)
|
|
@@ -36,13 +35,13 @@ export class RecordSchema<
|
|
|
36
35
|
|
|
37
36
|
isTypeOf<X extends { $type?: unknown }>(
|
|
38
37
|
value: X,
|
|
39
|
-
): value is X extends { $type:
|
|
38
|
+
): value is X extends { $type: T } ? X : X & { $type: T } {
|
|
40
39
|
return value.$type === this.$type
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
build<X extends Omit<
|
|
42
|
+
build<X extends Omit<Infer<S>, '$type'>>(
|
|
44
43
|
input: X,
|
|
45
|
-
): Simplify<Omit<X, '$type'> & { $type:
|
|
44
|
+
): Simplify<Omit<X, '$type'> & { $type: T }> {
|
|
46
45
|
return { ...input, $type: this.$type }
|
|
47
46
|
}
|
|
48
47
|
|
|
@@ -50,15 +49,15 @@ export class RecordSchema<
|
|
|
50
49
|
return this.isTypeOf<X>(value)
|
|
51
50
|
}
|
|
52
51
|
|
|
53
|
-
$build<X extends Omit<
|
|
52
|
+
$build<X extends Omit<Infer<S>, '$type'>>(input: X) {
|
|
54
53
|
return this.build<X>(input)
|
|
55
54
|
}
|
|
56
55
|
|
|
57
|
-
|
|
56
|
+
validateInContext(
|
|
58
57
|
input: unknown,
|
|
59
58
|
ctx: ValidatorContext,
|
|
60
|
-
): ValidationResult<
|
|
61
|
-
const result = ctx.validate(input, this.schema)
|
|
59
|
+
): ValidationResult<RecordSchemaOutput<T, S>> {
|
|
60
|
+
const result = ctx.validate(input, this.schema)
|
|
62
61
|
|
|
63
62
|
if (!result.success) {
|
|
64
63
|
return result
|
|
@@ -68,21 +67,22 @@ export class RecordSchema<
|
|
|
68
67
|
return ctx.issueInvalidPropertyValue(result.value, '$type', [this.$type])
|
|
69
68
|
}
|
|
70
69
|
|
|
71
|
-
return result
|
|
70
|
+
return result as ValidationResult<RecordSchemaOutput<T, S>>
|
|
72
71
|
}
|
|
73
72
|
}
|
|
74
73
|
|
|
75
|
-
export type RecordKeySchemaOutput<Key extends
|
|
76
|
-
|
|
77
|
-
: Key extends 'tid'
|
|
74
|
+
export type RecordKeySchemaOutput<Key extends LexiconRecordKey> =
|
|
75
|
+
Key extends 'any'
|
|
78
76
|
? string
|
|
79
|
-
: Key extends '
|
|
80
|
-
?
|
|
81
|
-
: Key extends
|
|
82
|
-
?
|
|
83
|
-
:
|
|
84
|
-
|
|
85
|
-
|
|
77
|
+
: Key extends 'tid'
|
|
78
|
+
? TidString
|
|
79
|
+
: Key extends 'nsid'
|
|
80
|
+
? NsidString
|
|
81
|
+
: Key extends `literal:${infer L extends string}`
|
|
82
|
+
? L
|
|
83
|
+
: never
|
|
84
|
+
|
|
85
|
+
export type RecordKeySchema<Key extends LexiconRecordKey> = Schema<
|
|
86
86
|
RecordKeySchemaOutput<Key>
|
|
87
87
|
>
|
|
88
88
|
|
|
@@ -91,7 +91,9 @@ const tidSchema = new StringSchema({ format: 'tid' })
|
|
|
91
91
|
const nsidSchema = new StringSchema({ format: 'nsid' })
|
|
92
92
|
const selfLiteralSchema = new LiteralSchema('self')
|
|
93
93
|
|
|
94
|
-
function recordKey<Key extends
|
|
94
|
+
function recordKey<Key extends LexiconRecordKey>(
|
|
95
|
+
key: Key,
|
|
96
|
+
): RecordKeySchema<Key> {
|
|
95
97
|
// @NOTE Use cached instances for common schemas
|
|
96
98
|
if (key === 'any') return keySchema as any
|
|
97
99
|
if (key === 'tid') return tidSchema as any
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import { Schema } from '../validation.js'
|
|
2
|
+
import { IntegerSchema } from './integer.js'
|
|
3
|
+
import { ObjectSchema } from './object.js'
|
|
4
|
+
import { OptionalSchema } from './optional.js'
|
|
5
|
+
import { RefSchema } from './ref.js'
|
|
6
|
+
import { StringSchema } from './string.js'
|
|
7
|
+
|
|
8
|
+
describe('RefSchema', () => {
|
|
9
|
+
describe('basic validation', () => {
|
|
10
|
+
it('validates through a simple string reference', () => {
|
|
11
|
+
const schema = new RefSchema(() => new StringSchema({}))
|
|
12
|
+
const result = schema.safeParse('hello')
|
|
13
|
+
expect(result.success).toBe(true)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('validates through an integer reference', () => {
|
|
17
|
+
const schema = new RefSchema(() => new IntegerSchema({}))
|
|
18
|
+
const result = schema.safeParse(42)
|
|
19
|
+
expect(result.success).toBe(true)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('rejects invalid input through reference', () => {
|
|
23
|
+
const schema = new RefSchema(() => new StringSchema({}))
|
|
24
|
+
const result = schema.safeParse(123)
|
|
25
|
+
expect(result.success).toBe(false)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('validates null rejection through reference', () => {
|
|
29
|
+
const schema = new RefSchema(() => new StringSchema({}))
|
|
30
|
+
const result = schema.safeParse(null)
|
|
31
|
+
expect(result.success).toBe(false)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('validates undefined rejection through reference', () => {
|
|
35
|
+
const schema = new RefSchema(() => new IntegerSchema({}))
|
|
36
|
+
const result = schema.safeParse(undefined)
|
|
37
|
+
expect(result.success).toBe(false)
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
describe('lazy schema resolution', () => {
|
|
42
|
+
it('does not call getter until first validation', () => {
|
|
43
|
+
let getterCalled = false
|
|
44
|
+
const schema = new RefSchema(() => {
|
|
45
|
+
getterCalled = true
|
|
46
|
+
return new StringSchema({})
|
|
47
|
+
})
|
|
48
|
+
expect(getterCalled).toBe(false)
|
|
49
|
+
|
|
50
|
+
schema.safeParse('test')
|
|
51
|
+
expect(getterCalled).toBe(true)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('caches the resolved schema', () => {
|
|
55
|
+
let callCount = 0
|
|
56
|
+
const schema = new RefSchema(() => {
|
|
57
|
+
callCount++
|
|
58
|
+
return new StringSchema({})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
schema.safeParse('first')
|
|
62
|
+
schema.safeParse('second')
|
|
63
|
+
schema.safeParse('third')
|
|
64
|
+
|
|
65
|
+
expect(callCount).toBe(1)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('throws error if getter is called multiple times', () => {
|
|
69
|
+
const schema = new RefSchema(() => new StringSchema({}))
|
|
70
|
+
|
|
71
|
+
// Access schema property to resolve it
|
|
72
|
+
schema.schema
|
|
73
|
+
|
|
74
|
+
// Try to access the original getter again (which should throw)
|
|
75
|
+
// This is internal behavior, but we're testing the protection mechanism
|
|
76
|
+
expect(() => {
|
|
77
|
+
// Force access to the cached schema property
|
|
78
|
+
const schemaValue = schema.schema
|
|
79
|
+
// This should work fine as it's now cached
|
|
80
|
+
expect(schemaValue).toBeDefined()
|
|
81
|
+
}).not.toThrow()
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
describe('with object schemas', () => {
|
|
86
|
+
it('validates objects through reference', () => {
|
|
87
|
+
const schema = new RefSchema(
|
|
88
|
+
() =>
|
|
89
|
+
new ObjectSchema({
|
|
90
|
+
name: new StringSchema({}),
|
|
91
|
+
age: new IntegerSchema({}),
|
|
92
|
+
}),
|
|
93
|
+
)
|
|
94
|
+
const result = schema.safeParse({
|
|
95
|
+
name: 'Alice',
|
|
96
|
+
age: 30,
|
|
97
|
+
})
|
|
98
|
+
expect(result.success).toBe(true)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('rejects invalid objects through reference', () => {
|
|
102
|
+
const schema = new RefSchema(
|
|
103
|
+
() =>
|
|
104
|
+
new ObjectSchema({
|
|
105
|
+
name: new StringSchema({}),
|
|
106
|
+
age: new IntegerSchema({}),
|
|
107
|
+
}),
|
|
108
|
+
)
|
|
109
|
+
const result = schema.safeParse({
|
|
110
|
+
name: 'Alice',
|
|
111
|
+
age: 'thirty',
|
|
112
|
+
})
|
|
113
|
+
expect(result.success).toBe(false)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('rejects objects with missing properties through reference', () => {
|
|
117
|
+
const schema = new RefSchema(
|
|
118
|
+
() =>
|
|
119
|
+
new ObjectSchema({
|
|
120
|
+
name: new StringSchema({}),
|
|
121
|
+
age: new IntegerSchema({}),
|
|
122
|
+
}),
|
|
123
|
+
)
|
|
124
|
+
const result = schema.safeParse({
|
|
125
|
+
name: 'Alice',
|
|
126
|
+
})
|
|
127
|
+
expect(result.success).toBe(false)
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
describe('with constrained schemas', () => {
|
|
132
|
+
it('validates string with minLength constraint through reference', () => {
|
|
133
|
+
const schema = new RefSchema(() => new StringSchema({ minLength: 5 }))
|
|
134
|
+
const result = schema.safeParse('hello')
|
|
135
|
+
expect(result.success).toBe(true)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('rejects string violating minLength through reference', () => {
|
|
139
|
+
const schema = new RefSchema(() => new StringSchema({ minLength: 5 }))
|
|
140
|
+
const result = schema.safeParse('hi')
|
|
141
|
+
expect(result.success).toBe(false)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('validates integer with range constraints through reference', () => {
|
|
145
|
+
const schema = new RefSchema(
|
|
146
|
+
() => new IntegerSchema({ minimum: 0, maximum: 100 }),
|
|
147
|
+
)
|
|
148
|
+
const result = schema.safeParse(50)
|
|
149
|
+
expect(result.success).toBe(true)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('rejects integer violating constraints through reference', () => {
|
|
153
|
+
const schema = new RefSchema(
|
|
154
|
+
() => new IntegerSchema({ minimum: 0, maximum: 100 }),
|
|
155
|
+
)
|
|
156
|
+
const result = schema.safeParse(150)
|
|
157
|
+
expect(result.success).toBe(false)
|
|
158
|
+
})
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
describe('circular references', () => {
|
|
162
|
+
it('prevents recursive getter calls', () => {
|
|
163
|
+
// Create a schema that would cause infinite recursion if not protected
|
|
164
|
+
let schema: RefSchema<any>
|
|
165
|
+
|
|
166
|
+
// eslint-disable-next-line prefer-const
|
|
167
|
+
schema = new RefSchema(() => {
|
|
168
|
+
// This would normally cause infinite recursion
|
|
169
|
+
// but the getter protection should prevent it
|
|
170
|
+
return schema.schema
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
// The first access causes stack overflow before the protection can kick in
|
|
174
|
+
expect(() => {
|
|
175
|
+
schema.safeParse('test')
|
|
176
|
+
}).toThrow()
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('supports indirect circular references', () => {
|
|
180
|
+
// Create two schemas that reference each other
|
|
181
|
+
// This demonstrates forward references are possible
|
|
182
|
+
|
|
183
|
+
type A = { value: string; ref?: B }
|
|
184
|
+
type B = { value: number; ref?: A }
|
|
185
|
+
|
|
186
|
+
const schemaA: Schema<A> = new ObjectSchema({
|
|
187
|
+
value: new StringSchema({}),
|
|
188
|
+
ref: new OptionalSchema(new RefSchema<B>((() => schemaB) as any)),
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
const schemaB: Schema<B> = new ObjectSchema({
|
|
192
|
+
value: new IntegerSchema({}),
|
|
193
|
+
ref: new OptionalSchema(new RefSchema<A>((() => schemaA) as any)),
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
expect(
|
|
197
|
+
schemaB.matches({
|
|
198
|
+
value: 42,
|
|
199
|
+
ref: {
|
|
200
|
+
value: 'hello',
|
|
201
|
+
ref: {
|
|
202
|
+
value: 3,
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
}),
|
|
206
|
+
).toBe(true)
|
|
207
|
+
|
|
208
|
+
expect(
|
|
209
|
+
schemaA.matches({
|
|
210
|
+
value: 'hello',
|
|
211
|
+
ref: {
|
|
212
|
+
value: 3,
|
|
213
|
+
ref: {
|
|
214
|
+
value: 'world',
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
}),
|
|
218
|
+
).toBe(true)
|
|
219
|
+
})
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
describe('multiple validations', () => {
|
|
223
|
+
it('validates multiple inputs correctly', () => {
|
|
224
|
+
const schema = new RefSchema(() => new StringSchema({ minLength: 3 }))
|
|
225
|
+
|
|
226
|
+
const result1 = schema.safeParse('hello')
|
|
227
|
+
expect(result1.success).toBe(true)
|
|
228
|
+
|
|
229
|
+
const result2 = schema.safeParse('hi')
|
|
230
|
+
expect(result2.success).toBe(false)
|
|
231
|
+
|
|
232
|
+
const result3 = schema.safeParse('world')
|
|
233
|
+
expect(result3.success).toBe(true)
|
|
234
|
+
|
|
235
|
+
const result4 = schema.safeParse('no')
|
|
236
|
+
expect(result4.success).toBe(false)
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('handles different types of validation failures', () => {
|
|
240
|
+
const schema = new RefSchema(
|
|
241
|
+
() =>
|
|
242
|
+
new ObjectSchema({
|
|
243
|
+
name: new StringSchema({ minLength: 2 }),
|
|
244
|
+
age: new IntegerSchema({ minimum: 0 }),
|
|
245
|
+
}),
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
const result1 = schema.safeParse({ name: 'A', age: 25 })
|
|
249
|
+
expect(result1.success).toBe(false)
|
|
250
|
+
|
|
251
|
+
const result2 = schema.safeParse({ name: 'Alice', age: -5 })
|
|
252
|
+
expect(result2.success).toBe(false)
|
|
253
|
+
|
|
254
|
+
const result3 = schema.safeParse({ name: 'Alice', age: 25 })
|
|
255
|
+
expect(result3.success).toBe(true)
|
|
256
|
+
})
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
describe('edge cases', () => {
|
|
260
|
+
it('handles empty string validation', () => {
|
|
261
|
+
const schema = new RefSchema(() => new StringSchema({}))
|
|
262
|
+
const result = schema.safeParse('')
|
|
263
|
+
expect(result.success).toBe(true)
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
it('handles zero validation', () => {
|
|
267
|
+
const schema = new RefSchema(() => new IntegerSchema({}))
|
|
268
|
+
const result = schema.safeParse(0)
|
|
269
|
+
expect(result.success).toBe(true)
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
it('rejects NaN through integer reference', () => {
|
|
273
|
+
const schema = new RefSchema(() => new IntegerSchema({}))
|
|
274
|
+
const result = schema.safeParse(NaN)
|
|
275
|
+
expect(result.success).toBe(false)
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
it('rejects Infinity through integer reference', () => {
|
|
279
|
+
const schema = new RefSchema(() => new IntegerSchema({}))
|
|
280
|
+
const result = schema.safeParse(Infinity)
|
|
281
|
+
expect(result.success).toBe(false)
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
it('rejects arrays when expecting string', () => {
|
|
285
|
+
const schema = new RefSchema(() => new StringSchema({}))
|
|
286
|
+
const result = schema.safeParse(['array'])
|
|
287
|
+
expect(result.success).toBe(false)
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
it('rejects objects when expecting string', () => {
|
|
291
|
+
const schema = new RefSchema(() => new StringSchema({}))
|
|
292
|
+
const result = schema.safeParse({ key: 'value' })
|
|
293
|
+
expect(result.success).toBe(false)
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
it('rejects booleans when expecting string', () => {
|
|
297
|
+
const schema = new RefSchema(() => new StringSchema({}))
|
|
298
|
+
const result = schema.safeParse(true)
|
|
299
|
+
expect(result.success).toBe(false)
|
|
300
|
+
})
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
describe('nested references', () => {
|
|
304
|
+
it('validates through nested RefSchema', () => {
|
|
305
|
+
const innerRef = new RefSchema(() => new StringSchema({ minLength: 3 }))
|
|
306
|
+
const outerRef = new RefSchema(() => innerRef)
|
|
307
|
+
|
|
308
|
+
const result = outerRef.safeParse('hello')
|
|
309
|
+
expect(result.success).toBe(true)
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
it('rejects invalid input through nested RefSchema', () => {
|
|
313
|
+
const innerRef = new RefSchema(() => new StringSchema({ minLength: 3 }))
|
|
314
|
+
const outerRef = new RefSchema(() => innerRef)
|
|
315
|
+
|
|
316
|
+
const result = outerRef.safeParse('hi')
|
|
317
|
+
expect(result.success).toBe(false)
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
it('validates with deeply nested references', () => {
|
|
321
|
+
const level3 = new RefSchema(() => new IntegerSchema({ minimum: 0 }))
|
|
322
|
+
const level2 = new RefSchema(() => level3)
|
|
323
|
+
const level1 = new RefSchema(() => level2)
|
|
324
|
+
|
|
325
|
+
const result = level1.safeParse(42)
|
|
326
|
+
expect(result.success).toBe(true)
|
|
327
|
+
})
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
describe('schema property access', () => {
|
|
331
|
+
it('allows direct access to resolved schema', () => {
|
|
332
|
+
const innerSchema = new StringSchema({ minLength: 5 })
|
|
333
|
+
const refSchema = new RefSchema(() => innerSchema)
|
|
334
|
+
|
|
335
|
+
const resolved = refSchema.schema
|
|
336
|
+
expect(resolved).toBe(innerSchema)
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
it('returns same instance on multiple schema property accesses', () => {
|
|
340
|
+
const innerSchema = new StringSchema({})
|
|
341
|
+
const refSchema = new RefSchema(() => innerSchema)
|
|
342
|
+
|
|
343
|
+
const first = refSchema.schema
|
|
344
|
+
const second = refSchema.schema
|
|
345
|
+
const third = refSchema.schema
|
|
346
|
+
|
|
347
|
+
expect(first).toBe(second)
|
|
348
|
+
expect(second).toBe(third)
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
it('resolves schema before validation', () => {
|
|
352
|
+
let resolved = false
|
|
353
|
+
const refSchema = new RefSchema(() => {
|
|
354
|
+
resolved = true
|
|
355
|
+
return new StringSchema({})
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
expect(resolved).toBe(false)
|
|
359
|
+
|
|
360
|
+
const schemaValue = refSchema.schema
|
|
361
|
+
expect(resolved).toBe(true)
|
|
362
|
+
expect(schemaValue).toBeDefined()
|
|
363
|
+
})
|
|
364
|
+
})
|
|
365
|
+
})
|
package/src/schema/ref.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Schema,
|
|
3
|
+
ValidationResult,
|
|
4
|
+
Validator,
|
|
5
|
+
ValidatorContext,
|
|
6
|
+
} from '../validation.js'
|
|
2
7
|
|
|
3
8
|
export type RefSchemaGetter<V> = () => Validator<V>
|
|
4
9
|
|
|
5
|
-
export class RefSchema<V = any> extends
|
|
6
|
-
readonly lexiconType = 'ref' as const
|
|
7
|
-
|
|
10
|
+
export class RefSchema<V = any> extends Schema<V> {
|
|
8
11
|
#getter: RefSchemaGetter<V>
|
|
9
12
|
|
|
10
13
|
constructor(getter: RefSchemaGetter<V>) {
|
|
@@ -34,7 +37,7 @@ export class RefSchema<V = any> extends Validator<V> {
|
|
|
34
37
|
return value
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
|
|
40
|
+
validateInContext(
|
|
38
41
|
input: unknown,
|
|
39
42
|
ctx: ValidatorContext,
|
|
40
43
|
): ValidationResult<V> {
|