@atproto/lex-schema 0.0.9 → 0.0.10
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 +34 -0
- package/LICENSE.txt +1 -1
- package/dist/core/$type.d.ts +11 -0
- package/dist/core/$type.d.ts.map +1 -1
- package/dist/core/$type.js +4 -0
- package/dist/core/$type.js.map +1 -1
- package/dist/core/schema.d.ts +31 -24
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +38 -8
- package/dist/core/schema.js.map +1 -1
- package/dist/core/string-format.d.ts +35 -35
- package/dist/core/string-format.d.ts.map +1 -1
- package/dist/core/string-format.js +49 -91
- package/dist/core/string-format.js.map +1 -1
- package/dist/core/validation-issue.js +1 -1
- package/dist/core/validation-issue.js.map +1 -1
- package/dist/core/validator.d.ts +53 -32
- package/dist/core/validator.d.ts.map +1 -1
- package/dist/core/validator.js +18 -22
- package/dist/core/validator.js.map +1 -1
- package/dist/external.d.ts +0 -85
- package/dist/external.d.ts.map +1 -1
- package/dist/external.js +0 -164
- package/dist/external.js.map +1 -1
- package/dist/helpers.d.ts +10 -5
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +3 -3
- package/dist/helpers.js.map +1 -1
- package/dist/schema/array.d.ts +9 -5
- package/dist/schema/array.d.ts.map +1 -1
- package/dist/schema/array.js +14 -5
- package/dist/schema/array.js.map +1 -1
- package/dist/schema/blob.d.ts +9 -7
- package/dist/schema/blob.d.ts.map +1 -1
- package/dist/schema/blob.js +9 -5
- package/dist/schema/blob.js.map +1 -1
- package/dist/schema/boolean.d.ts +3 -7
- package/dist/schema/boolean.d.ts.map +1 -1
- package/dist/schema/boolean.js +6 -7
- package/dist/schema/boolean.js.map +1 -1
- package/dist/schema/bytes.d.ts +3 -2
- package/dist/schema/bytes.d.ts.map +1 -1
- package/dist/schema/bytes.js +7 -3
- package/dist/schema/bytes.js.map +1 -1
- package/dist/schema/cid.d.ts +7 -7
- package/dist/schema/cid.d.ts.map +1 -1
- package/dist/schema/cid.js +5 -1
- package/dist/schema/cid.js.map +1 -1
- package/dist/schema/custom.d.ts +6 -5
- package/dist/schema/custom.d.ts.map +1 -1
- package/dist/schema/custom.js +10 -4
- package/dist/schema/custom.js.map +1 -1
- package/dist/schema/dict.d.ts +8 -8
- package/dist/schema/dict.d.ts.map +1 -1
- package/dist/schema/dict.js +11 -2
- package/dist/schema/dict.js.map +1 -1
- package/dist/schema/discriminated-union.d.ts +21 -14
- package/dist/schema/discriminated-union.d.ts.map +1 -1
- package/dist/schema/discriminated-union.js +7 -0
- package/dist/schema/discriminated-union.js.map +1 -1
- package/dist/schema/enum.d.ts +7 -9
- package/dist/schema/enum.d.ts.map +1 -1
- package/dist/schema/enum.js +8 -4
- package/dist/schema/enum.js.map +1 -1
- package/dist/schema/integer.d.ts +5 -5
- package/dist/schema/integer.d.ts.map +1 -1
- package/dist/schema/integer.js +9 -5
- package/dist/schema/integer.js.map +1 -1
- package/dist/schema/intersection.d.ts +4 -4
- package/dist/schema/intersection.d.ts.map +1 -1
- package/dist/schema/intersection.js +5 -0
- package/dist/schema/intersection.js.map +1 -1
- package/dist/schema/literal.d.ts +6 -9
- package/dist/schema/literal.d.ts.map +1 -1
- package/dist/schema/literal.js +7 -4
- package/dist/schema/literal.js.map +1 -1
- package/dist/schema/never.d.ts +3 -2
- package/dist/schema/never.d.ts.map +1 -1
- package/dist/schema/never.js +5 -1
- package/dist/schema/never.js.map +1 -1
- package/dist/schema/null.d.ts +4 -3
- package/dist/schema/null.d.ts.map +1 -1
- package/dist/schema/null.js +6 -4
- package/dist/schema/null.js.map +1 -1
- package/dist/schema/nullable.d.ts +6 -5
- package/dist/schema/nullable.d.ts.map +1 -1
- package/dist/schema/nullable.js +9 -5
- package/dist/schema/nullable.js.map +1 -1
- package/dist/schema/object.d.ts +10 -8
- package/dist/schema/object.d.ts.map +1 -1
- package/dist/schema/object.js +11 -3
- package/dist/schema/object.js.map +1 -1
- package/dist/schema/optional.d.ts +7 -5
- package/dist/schema/optional.d.ts.map +1 -1
- package/dist/schema/optional.js +14 -6
- package/dist/schema/optional.js.map +1 -1
- package/dist/schema/params.d.ts +24 -13
- package/dist/schema/params.d.ts.map +1 -1
- package/dist/schema/params.js +47 -25
- package/dist/schema/params.js.map +1 -1
- package/dist/schema/payload.d.ts +12 -9
- package/dist/schema/payload.d.ts.map +1 -1
- package/dist/schema/payload.js +11 -0
- package/dist/schema/payload.js.map +1 -1
- package/dist/schema/permission-set.d.ts +1 -0
- package/dist/schema/permission-set.d.ts.map +1 -1
- package/dist/schema/permission-set.js +5 -0
- package/dist/schema/permission-set.js.map +1 -1
- package/dist/schema/permission.d.ts +6 -5
- package/dist/schema/permission.d.ts.map +1 -1
- package/dist/schema/permission.js +5 -0
- package/dist/schema/permission.js.map +1 -1
- package/dist/schema/procedure.d.ts +2 -1
- package/dist/schema/procedure.d.ts.map +1 -1
- package/dist/schema/procedure.js +5 -0
- package/dist/schema/procedure.js.map +1 -1
- package/dist/schema/query.d.ts +2 -1
- package/dist/schema/query.d.ts.map +1 -1
- package/dist/schema/query.js +5 -0
- package/dist/schema/query.js.map +1 -1
- package/dist/schema/record.d.ts +48 -30
- package/dist/schema/record.d.ts.map +1 -1
- package/dist/schema/record.js +12 -9
- package/dist/schema/record.js.map +1 -1
- package/dist/schema/ref.d.ts +9 -6
- package/dist/schema/ref.d.ts.map +1 -1
- package/dist/schema/ref.js +9 -16
- package/dist/schema/ref.js.map +1 -1
- package/dist/schema/refine.d.ts +4 -4
- package/dist/schema/refine.d.ts.map +1 -1
- package/dist/schema/refine.js.map +1 -1
- package/dist/schema/regexp.d.ts +4 -3
- package/dist/schema/regexp.d.ts.map +1 -1
- package/dist/schema/regexp.js +5 -0
- package/dist/schema/regexp.js.map +1 -1
- package/dist/schema/string.d.ts +7 -8
- package/dist/schema/string.d.ts.map +1 -1
- package/dist/schema/string.js +13 -19
- package/dist/schema/string.js.map +1 -1
- package/dist/schema/subscription.d.ts +2 -1
- package/dist/schema/subscription.d.ts.map +1 -1
- package/dist/schema/subscription.js +5 -0
- package/dist/schema/subscription.js.map +1 -1
- package/dist/schema/token.d.ts +6 -5
- package/dist/schema/token.d.ts.map +1 -1
- package/dist/schema/token.js +5 -0
- package/dist/schema/token.js.map +1 -1
- package/dist/schema/typed-object.d.ts +43 -26
- package/dist/schema/typed-object.d.ts.map +1 -1
- package/dist/schema/typed-object.js +6 -3
- package/dist/schema/typed-object.js.map +1 -1
- package/dist/schema/typed-ref.d.ts +16 -25
- package/dist/schema/typed-ref.d.ts.map +1 -1
- package/dist/schema/typed-ref.js +7 -17
- package/dist/schema/typed-ref.js.map +1 -1
- package/dist/schema/typed-union.d.ts +9 -21
- package/dist/schema/typed-union.d.ts.map +1 -1
- package/dist/schema/typed-union.js +15 -11
- package/dist/schema/typed-union.js.map +1 -1
- package/dist/schema/union.d.ts +6 -6
- package/dist/schema/union.d.ts.map +1 -1
- package/dist/schema/union.js +7 -5
- package/dist/schema/union.js.map +1 -1
- package/dist/schema/unknown-object.d.ts +5 -4
- package/dist/schema/unknown-object.d.ts.map +1 -1
- package/dist/schema/unknown-object.js +5 -1
- package/dist/schema/unknown-object.js.map +1 -1
- package/dist/schema/unknown.d.ts +3 -2
- package/dist/schema/unknown.d.ts.map +1 -1
- package/dist/schema/unknown.js +5 -1
- package/dist/schema/unknown.js.map +1 -1
- package/dist/schema/with-default.d.ts +9 -0
- package/dist/schema/with-default.d.ts.map +1 -0
- package/dist/schema/with-default.js +27 -0
- package/dist/schema/with-default.js.map +1 -0
- package/dist/schema.d.ts +2 -2
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +2 -4
- package/dist/schema.js.map +1 -1
- package/dist/util/assertion-util.d.ts +0 -6
- package/dist/util/assertion-util.d.ts.map +1 -1
- package/dist/util/assertion-util.js +0 -28
- package/dist/util/assertion-util.js.map +1 -1
- package/dist/util/memoize.d.ts +2 -2
- package/dist/util/memoize.d.ts.map +1 -1
- package/dist/util/memoize.js +23 -39
- package/dist/util/memoize.js.map +1 -1
- package/package.json +3 -3
- package/src/core/$type.test.ts +20 -0
- package/src/core/$type.ts +30 -0
- package/src/core/schema.ts +86 -38
- package/src/core/string-format.ts +119 -158
- package/src/core/validation-issue.ts +1 -1
- package/src/core/validator.ts +93 -53
- package/src/external.ts +0 -404
- package/src/helpers.test.ts +22 -21
- package/src/helpers.ts +14 -14
- package/src/schema/array.test.ts +38 -40
- package/src/schema/array.ts +35 -13
- package/src/schema/blob.test.ts +21 -21
- package/src/schema/blob.ts +19 -17
- package/src/schema/boolean.test.ts +9 -8
- package/src/schema/boolean.ts +7 -13
- package/src/schema/bytes.test.ts +13 -13
- package/src/schema/bytes.ts +13 -8
- package/src/schema/cid.test.ts +3 -3
- package/src/schema/cid.ts +13 -12
- package/src/schema/custom.test.ts +26 -26
- package/src/schema/custom.ts +20 -13
- package/src/schema/dict.test.ts +21 -39
- package/src/schema/dict.ts +28 -19
- package/src/schema/discriminated-union.test.ts +128 -128
- package/src/schema/discriminated-union.ts +45 -26
- package/src/schema/enum.test.ts +17 -16
- package/src/schema/enum.ts +16 -16
- package/src/schema/integer.test.ts +22 -21
- package/src/schema/integer.ts +12 -9
- package/src/schema/intersection.test.ts +10 -10
- package/src/schema/intersection.ts +17 -14
- package/src/schema/literal.test.ts +35 -34
- package/src/schema/literal.ts +12 -15
- package/src/schema/never.test.ts +5 -5
- package/src/schema/never.ts +7 -2
- package/src/schema/null.test.ts +3 -3
- package/src/schema/null.ts +9 -9
- package/src/schema/nullable.test.ts +31 -42
- package/src/schema/nullable.ts +17 -9
- package/src/schema/object.test.ts +10 -12
- package/src/schema/object.ts +27 -18
- package/src/schema/optional.test.ts +21 -28
- package/src/schema/optional.ts +27 -10
- package/src/schema/params.test.ts +471 -47
- package/src/schema/params.ts +72 -38
- package/src/schema/payload.test.ts +150 -156
- package/src/schema/payload.ts +35 -19
- package/src/schema/permission-set.test.ts +206 -273
- package/src/schema/permission-set.ts +8 -0
- package/src/schema/permission.test.ts +177 -177
- package/src/schema/permission.ts +13 -5
- package/src/schema/procedure.test.ts +183 -242
- package/src/schema/procedure.ts +18 -5
- package/src/schema/query.test.ts +186 -200
- package/src/schema/query.ts +16 -4
- package/src/schema/record.test.ts +121 -101
- package/src/schema/record.ts +74 -40
- package/src/schema/ref.test.ts +101 -118
- package/src/schema/ref.ts +33 -28
- package/src/schema/refine.test.ts +28 -28
- package/src/schema/refine.ts +23 -20
- package/src/schema/regexp.test.ts +29 -33
- package/src/schema/regexp.ts +11 -7
- package/src/schema/string.test.ts +35 -35
- package/src/schema/string.ts +24 -33
- package/src/schema/subscription.test.ts +259 -387
- package/src/schema/subscription.ts +16 -4
- package/src/schema/token.test.ts +47 -324
- package/src/schema/token.ts +14 -7
- package/src/schema/typed-object.test.ts +98 -81
- package/src/schema/typed-object.ts +68 -33
- package/src/schema/typed-ref.test.ts +206 -234
- package/src/schema/typed-ref.ts +40 -42
- package/src/schema/typed-union.test.ts +40 -64
- package/src/schema/typed-union.ts +36 -58
- package/src/schema/union.test.ts +17 -27
- package/src/schema/union.ts +20 -16
- package/src/schema/unknown-object.test.ts +8 -8
- package/src/schema/unknown-object.ts +9 -7
- package/src/schema/unknown.test.ts +4 -4
- package/src/schema/unknown.ts +7 -5
- package/src/schema/with-default.ts +35 -0
- package/src/schema.ts +2 -6
- package/src/util/assertion-util.ts +0 -39
- package/src/util/memoize.ts +26 -46
- package/dist/schema/_parameters.d.ts +0 -17
- package/dist/schema/_parameters.d.ts.map +0 -1
- package/dist/schema/_parameters.js +0 -20
- package/dist/schema/_parameters.js.map +0 -1
- package/src/schema/_parameters.test.ts +0 -417
- package/src/schema/_parameters.ts +0 -26
package/src/schema/typed-ref.ts
CHANGED
|
@@ -1,28 +1,31 @@
|
|
|
1
1
|
import {
|
|
2
|
+
$Typed,
|
|
3
|
+
InferInput,
|
|
4
|
+
InferOutput,
|
|
2
5
|
Schema,
|
|
3
|
-
|
|
6
|
+
ValidationContext,
|
|
4
7
|
Validator,
|
|
5
|
-
ValidatorContext,
|
|
6
8
|
} from '../core.js'
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export type TypedRefGetter<V extends { $type?: string } = any> =
|
|
15
|
-
() => TypedRefSchemaValidator<V>
|
|
10
|
+
export interface TypedObjectValidator<
|
|
11
|
+
TInput extends { $type?: string } = { $type?: string },
|
|
12
|
+
TOutput extends TInput = TInput,
|
|
13
|
+
> extends Validator<TInput, TOutput> {
|
|
14
|
+
$type: NonNullable<TOutput['$type']>
|
|
15
|
+
}
|
|
16
16
|
|
|
17
|
-
export type
|
|
18
|
-
|
|
17
|
+
export type TypedRefGetter<out TValidator extends TypedObjectValidator> =
|
|
18
|
+
() => TValidator
|
|
19
19
|
|
|
20
|
-
export class TypedRefSchema<
|
|
21
|
-
|
|
20
|
+
export class TypedRefSchema<
|
|
21
|
+
const TValidator extends TypedObjectValidator = TypedObjectValidator,
|
|
22
|
+
> extends Schema<
|
|
23
|
+
$Typed<InferInput<TValidator>>,
|
|
24
|
+
$Typed<InferOutput<TValidator>>
|
|
22
25
|
> {
|
|
23
|
-
#getter: TypedRefGetter<
|
|
26
|
+
#getter: TypedRefGetter<TValidator>
|
|
24
27
|
|
|
25
|
-
constructor(getter: TypedRefGetter<
|
|
28
|
+
constructor(getter: TypedRefGetter<TValidator>) {
|
|
26
29
|
// @NOTE In order to avoid circular dependency issues, we don't resolve
|
|
27
30
|
// the schema here. Instead, we resolve it lazily when first accessed.
|
|
28
31
|
|
|
@@ -31,43 +34,38 @@ export class TypedRefSchema<V extends { $type?: string } = any> extends Schema<
|
|
|
31
34
|
this.#getter = getter
|
|
32
35
|
}
|
|
33
36
|
|
|
34
|
-
get
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
// Prevents a getter from depending on itself recursively, also allows GC to
|
|
38
|
-
// clean up the getter function.
|
|
39
|
-
this.#getter = throwAlreadyCalled
|
|
40
|
-
|
|
41
|
-
// Cache the resolved schema on the instance
|
|
42
|
-
Object.defineProperty(this, 'schema', {
|
|
43
|
-
value,
|
|
44
|
-
writable: false,
|
|
45
|
-
enumerable: false,
|
|
46
|
-
configurable: true,
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
return value
|
|
37
|
+
get validator(): TValidator {
|
|
38
|
+
return this.#getter.call(null)
|
|
50
39
|
}
|
|
51
40
|
|
|
52
|
-
get $type():
|
|
53
|
-
return this.
|
|
41
|
+
get $type(): TValidator['$type'] {
|
|
42
|
+
return this.validator.$type
|
|
54
43
|
}
|
|
55
44
|
|
|
56
|
-
validateInContext(
|
|
57
|
-
input
|
|
58
|
-
ctx: ValidatorContext,
|
|
59
|
-
): ValidationResult<TypedRefSchemaOutput<V>> {
|
|
60
|
-
const result = ctx.validate(input, this.schema)
|
|
45
|
+
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
46
|
+
const result = ctx.validate(input, this.validator)
|
|
61
47
|
if (!result.success) return result
|
|
62
48
|
|
|
63
49
|
if (result.value.$type !== this.$type) {
|
|
64
50
|
return ctx.issueInvalidPropertyValue(result.value, '$type', [this.$type])
|
|
65
51
|
}
|
|
66
52
|
|
|
67
|
-
return result
|
|
53
|
+
return result
|
|
68
54
|
}
|
|
69
55
|
}
|
|
70
56
|
|
|
71
|
-
|
|
72
|
-
|
|
57
|
+
/*@__NO_SIDE_EFFECTS__*/
|
|
58
|
+
export function typedRef<const TValidator extends TypedObjectValidator>(
|
|
59
|
+
get: TypedRefGetter<TValidator>,
|
|
60
|
+
): TypedRefSchema<TValidator>
|
|
61
|
+
export function typedRef<
|
|
62
|
+
TInput extends { $type?: string },
|
|
63
|
+
TOutput extends TInput = TInput,
|
|
64
|
+
>(
|
|
65
|
+
get: TypedRefGetter<TypedObjectValidator<TInput, TOutput>>,
|
|
66
|
+
): TypedRefSchema<TypedObjectValidator<TInput, TOutput>>
|
|
67
|
+
export function typedRef<const TValidator extends TypedObjectValidator>(
|
|
68
|
+
get: TypedRefGetter<TValidator>,
|
|
69
|
+
): TypedRefSchema<TValidator> {
|
|
70
|
+
return new TypedRefSchema<TValidator>(get)
|
|
73
71
|
}
|
|
@@ -1,42 +1,42 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
2
|
+
import { integer } from './integer.js'
|
|
3
|
+
import { object } from './object.js'
|
|
4
|
+
import { string } from './string.js'
|
|
5
|
+
import { typedObject } from './typed-object.js'
|
|
6
|
+
import { typedRef } from './typed-ref.js'
|
|
7
|
+
import { typedUnion } from './typed-union.js'
|
|
8
8
|
|
|
9
9
|
describe('TypedUnionSchema', () => {
|
|
10
|
-
const personSchema =
|
|
10
|
+
const personSchema = typedObject(
|
|
11
11
|
'app.bsky.actor.person',
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
'main',
|
|
13
|
+
object({
|
|
14
|
+
name: string(),
|
|
15
|
+
age: integer(),
|
|
15
16
|
}),
|
|
16
17
|
)
|
|
17
18
|
|
|
18
|
-
const postSchema =
|
|
19
|
+
const postSchema = typedObject(
|
|
19
20
|
'app.bsky.feed.post',
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
'main',
|
|
22
|
+
object({
|
|
23
|
+
text: string(),
|
|
24
|
+
createdAt: string(),
|
|
23
25
|
}),
|
|
24
26
|
)
|
|
25
27
|
|
|
26
|
-
const commentSchema =
|
|
28
|
+
const commentSchema = typedObject(
|
|
27
29
|
'app.bsky.feed.comment',
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
'main',
|
|
31
|
+
object({
|
|
32
|
+
text: string(),
|
|
33
|
+
parentUri: string(),
|
|
31
34
|
}),
|
|
32
35
|
)
|
|
33
36
|
|
|
34
37
|
describe('closed union', () => {
|
|
35
|
-
const schema =
|
|
36
|
-
[
|
|
37
|
-
new TypedRefSchema(() => personSchema),
|
|
38
|
-
new TypedRefSchema(() => postSchema),
|
|
39
|
-
],
|
|
38
|
+
const schema = typedUnion(
|
|
39
|
+
[typedRef(() => personSchema), typedRef(() => postSchema)],
|
|
40
40
|
true,
|
|
41
41
|
)
|
|
42
42
|
|
|
@@ -123,11 +123,8 @@ describe('TypedUnionSchema', () => {
|
|
|
123
123
|
})
|
|
124
124
|
|
|
125
125
|
describe('open union', () => {
|
|
126
|
-
const schema =
|
|
127
|
-
[
|
|
128
|
-
new TypedRefSchema(() => personSchema),
|
|
129
|
-
new TypedRefSchema(() => postSchema),
|
|
130
|
-
],
|
|
126
|
+
const schema = typedUnion(
|
|
127
|
+
[typedRef(() => personSchema), typedRef(() => postSchema)],
|
|
131
128
|
false,
|
|
132
129
|
)
|
|
133
130
|
|
|
@@ -208,11 +205,11 @@ describe('TypedUnionSchema', () => {
|
|
|
208
205
|
})
|
|
209
206
|
|
|
210
207
|
describe('with three types', () => {
|
|
211
|
-
const schema =
|
|
208
|
+
const schema = typedUnion(
|
|
212
209
|
[
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
210
|
+
typedRef(() => personSchema),
|
|
211
|
+
typedRef(() => postSchema),
|
|
212
|
+
typedRef(() => commentSchema),
|
|
216
213
|
],
|
|
217
214
|
true,
|
|
218
215
|
)
|
|
@@ -254,10 +251,7 @@ describe('TypedUnionSchema', () => {
|
|
|
254
251
|
})
|
|
255
252
|
|
|
256
253
|
describe('with single type', () => {
|
|
257
|
-
const schema =
|
|
258
|
-
[new TypedRefSchema(() => personSchema)],
|
|
259
|
-
true,
|
|
260
|
-
)
|
|
254
|
+
const schema = typedUnion([typedRef(() => personSchema)], true)
|
|
261
255
|
|
|
262
256
|
it('validates the single type', () => {
|
|
263
257
|
const result = schema.safeParse({
|
|
@@ -279,11 +273,8 @@ describe('TypedUnionSchema', () => {
|
|
|
279
273
|
})
|
|
280
274
|
|
|
281
275
|
describe('$types getter', () => {
|
|
282
|
-
const schema =
|
|
283
|
-
[
|
|
284
|
-
new TypedRefSchema(() => personSchema),
|
|
285
|
-
new TypedRefSchema(() => postSchema),
|
|
286
|
-
],
|
|
276
|
+
const schema = typedUnion(
|
|
277
|
+
[typedRef(() => personSchema), typedRef(() => postSchema)],
|
|
287
278
|
true,
|
|
288
279
|
)
|
|
289
280
|
|
|
@@ -296,16 +287,13 @@ describe('TypedUnionSchema', () => {
|
|
|
296
287
|
})
|
|
297
288
|
|
|
298
289
|
describe('refsMap getter', () => {
|
|
299
|
-
const schema =
|
|
300
|
-
[
|
|
301
|
-
new TypedRefSchema(() => personSchema),
|
|
302
|
-
new TypedRefSchema(() => postSchema),
|
|
303
|
-
],
|
|
290
|
+
const schema = typedUnion(
|
|
291
|
+
[typedRef(() => personSchema), typedRef(() => postSchema)],
|
|
304
292
|
true,
|
|
305
293
|
)
|
|
306
294
|
|
|
307
295
|
it('returns map of $type to ref schema', () => {
|
|
308
|
-
const refsMap = schema.
|
|
296
|
+
const refsMap = schema.validatorsMap
|
|
309
297
|
expect(refsMap.size).toBe(2)
|
|
310
298
|
expect(refsMap.has('app.bsky.actor.person')).toBe(true)
|
|
311
299
|
expect(refsMap.has('app.bsky.feed.post')).toBe(true)
|
|
@@ -313,11 +301,8 @@ describe('TypedUnionSchema', () => {
|
|
|
313
301
|
})
|
|
314
302
|
|
|
315
303
|
describe('edge cases', () => {
|
|
316
|
-
const schema =
|
|
317
|
-
[
|
|
318
|
-
new TypedRefSchema(() => personSchema),
|
|
319
|
-
new TypedRefSchema(() => postSchema),
|
|
320
|
-
],
|
|
304
|
+
const schema = typedUnion(
|
|
305
|
+
[typedRef(() => personSchema), typedRef(() => postSchema)],
|
|
321
306
|
true,
|
|
322
307
|
)
|
|
323
308
|
|
|
@@ -330,10 +315,7 @@ describe('TypedUnionSchema', () => {
|
|
|
330
315
|
})
|
|
331
316
|
|
|
332
317
|
it('validates object with $type as empty string in open union', () => {
|
|
333
|
-
const openSchema =
|
|
334
|
-
[new TypedRefSchema(() => personSchema)],
|
|
335
|
-
false,
|
|
336
|
-
)
|
|
318
|
+
const openSchema = typedUnion([typedRef(() => personSchema)], false)
|
|
337
319
|
const result = openSchema.safeParse({
|
|
338
320
|
$type: '',
|
|
339
321
|
someProperty: 'value',
|
|
@@ -361,18 +343,12 @@ describe('TypedUnionSchema', () => {
|
|
|
361
343
|
|
|
362
344
|
describe('closed property', () => {
|
|
363
345
|
it('exposes closed property as true', () => {
|
|
364
|
-
const schema =
|
|
365
|
-
[new TypedRefSchema(() => personSchema)],
|
|
366
|
-
true,
|
|
367
|
-
)
|
|
346
|
+
const schema = typedUnion([typedRef(() => personSchema)], true)
|
|
368
347
|
expect(schema.closed).toBe(true)
|
|
369
348
|
})
|
|
370
349
|
|
|
371
350
|
it('exposes closed property as false', () => {
|
|
372
|
-
const schema =
|
|
373
|
-
[new TypedRefSchema(() => personSchema)],
|
|
374
|
-
false,
|
|
375
|
-
)
|
|
351
|
+
const schema = typedUnion([typedRef(() => personSchema)], false)
|
|
376
352
|
expect(schema.closed).toBe(false)
|
|
377
353
|
})
|
|
378
354
|
})
|
|
@@ -1,53 +1,28 @@
|
|
|
1
1
|
import { isPlainObject } from '@atproto/lex-data'
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
InferInput,
|
|
4
|
+
InferOutput,
|
|
5
5
|
Schema,
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
Unknown$TypedObject,
|
|
7
|
+
ValidationContext,
|
|
8
8
|
} from '../core.js'
|
|
9
9
|
import { lazyProperty } from '../util/lazy-property.js'
|
|
10
|
-
import { TypedRefSchema
|
|
11
|
-
|
|
12
|
-
export type TypedRef<T extends { $type?: string }> = TypedRefSchemaOutput<T>
|
|
13
|
-
|
|
14
|
-
export type TypedObject = { $type: string } & {
|
|
15
|
-
// In order to prevent places that expect an open union from accepting an
|
|
16
|
-
// invalid version of the known typed objects, we need to prevent any other
|
|
17
|
-
// properties from being present.
|
|
18
|
-
//
|
|
19
|
-
// For example, if an open union expects:
|
|
20
|
-
// ```ts
|
|
21
|
-
// TypedObject | { $type: 'A'; a: number }
|
|
22
|
-
// ```
|
|
23
|
-
// we don't want it to accept:
|
|
24
|
-
// ```ts
|
|
25
|
-
// { $type: 'A' }
|
|
26
|
-
// ```
|
|
27
|
-
// Which would be the case as `{ $type: 'A' }` is a valid
|
|
28
|
-
// `TypedObject`. By adding an index signature that forbids any
|
|
29
|
-
// property, we ensure that only valid known typed objects can be used.
|
|
30
|
-
[K in string]: Restricted<'Unknown property'>
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
type TypedRefSchemasToUnion<T extends readonly TypedRefSchema[]> = {
|
|
34
|
-
[K in keyof T]: Infer<T[K]>
|
|
35
|
-
}[number]
|
|
36
|
-
|
|
37
|
-
export type TypedUnionSchemaOutput<
|
|
38
|
-
TypedRefs extends readonly TypedRefSchema[],
|
|
39
|
-
Closed extends boolean,
|
|
40
|
-
> = Closed extends true
|
|
41
|
-
? TypedRefSchemasToUnion<TypedRefs>
|
|
42
|
-
: TypedRefSchemasToUnion<TypedRefs> | TypedObject
|
|
10
|
+
import { TypedRefSchema } from './typed-ref.js'
|
|
43
11
|
|
|
44
12
|
export class TypedUnionSchema<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
> extends Schema<
|
|
13
|
+
const TValidators extends readonly TypedRefSchema[] = [],
|
|
14
|
+
const TClosed extends boolean = boolean,
|
|
15
|
+
> extends Schema<
|
|
16
|
+
TClosed extends true
|
|
17
|
+
? InferInput<TValidators[number]>
|
|
18
|
+
: InferInput<TValidators[number]> | Unknown$TypedObject,
|
|
19
|
+
TClosed extends true
|
|
20
|
+
? InferOutput<TValidators[number]>
|
|
21
|
+
: InferOutput<TValidators[number]> | Unknown$TypedObject
|
|
22
|
+
> {
|
|
48
23
|
constructor(
|
|
49
|
-
protected readonly
|
|
50
|
-
public readonly closed:
|
|
24
|
+
protected readonly validators: TValidators,
|
|
25
|
+
public readonly closed: TClosed,
|
|
51
26
|
) {
|
|
52
27
|
// @NOTE In order to avoid circular dependency issues, we don't access the
|
|
53
28
|
// refs's schema (or $type) here. Instead, we access them lazily when first
|
|
@@ -56,42 +31,45 @@ export class TypedUnionSchema<
|
|
|
56
31
|
super()
|
|
57
32
|
}
|
|
58
33
|
|
|
59
|
-
get
|
|
60
|
-
const map = new Map<unknown,
|
|
61
|
-
for (const ref of this.
|
|
34
|
+
get validatorsMap(): Map<unknown, TValidators[number]> {
|
|
35
|
+
const map = new Map<unknown, TValidators[number]>()
|
|
36
|
+
for (const ref of this.validators) map.set(ref.$type, ref)
|
|
62
37
|
|
|
63
|
-
return lazyProperty(this, '
|
|
38
|
+
return lazyProperty(this, 'validatorsMap', map)
|
|
64
39
|
}
|
|
65
40
|
|
|
66
41
|
get $types() {
|
|
67
|
-
return Array.from(this.
|
|
42
|
+
return Array.from(this.validatorsMap.keys())
|
|
68
43
|
}
|
|
69
44
|
|
|
70
|
-
validateInContext(
|
|
71
|
-
input: unknown,
|
|
72
|
-
ctx: ValidatorContext,
|
|
73
|
-
): ValidationResult<TypedUnionSchemaOutput<TypedRefs, Closed>> {
|
|
45
|
+
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
74
46
|
if (!isPlainObject(input) || !('$type' in input)) {
|
|
75
47
|
return ctx.issueInvalidType(input, '$typed')
|
|
76
48
|
}
|
|
77
49
|
|
|
78
50
|
const { $type } = input
|
|
79
51
|
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
return result as ValidationResult<
|
|
84
|
-
TypedUnionSchemaOutput<TypedRefs, Closed>
|
|
85
|
-
>
|
|
52
|
+
const validator = this.validatorsMap.get($type)
|
|
53
|
+
if (validator) {
|
|
54
|
+
return ctx.validate(input, validator)
|
|
86
55
|
}
|
|
87
56
|
|
|
88
57
|
if (this.closed) {
|
|
89
58
|
return ctx.issueInvalidPropertyValue(input, '$type', this.$types)
|
|
90
59
|
}
|
|
60
|
+
|
|
91
61
|
if (typeof $type !== 'string') {
|
|
92
62
|
return ctx.issueInvalidPropertyType(input, '$type', 'string')
|
|
93
63
|
}
|
|
94
64
|
|
|
95
|
-
return ctx.success(input
|
|
65
|
+
return ctx.success(input)
|
|
96
66
|
}
|
|
97
67
|
}
|
|
68
|
+
|
|
69
|
+
/*@__NO_SIDE_EFFECTS__*/
|
|
70
|
+
export function typedUnion<
|
|
71
|
+
const R extends readonly TypedRefSchema[],
|
|
72
|
+
const C extends boolean,
|
|
73
|
+
>(refs: R, closed: C) {
|
|
74
|
+
return new TypedUnionSchema<R, C>(refs, closed)
|
|
75
|
+
}
|
package/src/schema/union.test.ts
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
2
|
+
import { boolean } from './boolean.js'
|
|
3
|
+
import { integer } from './integer.js'
|
|
4
|
+
import { object } from './object.js'
|
|
5
|
+
import { string } from './string.js'
|
|
6
|
+
import { union } from './union.js'
|
|
7
7
|
|
|
8
8
|
describe('UnionSchema', () => {
|
|
9
|
-
const stringOrNumber =
|
|
10
|
-
new StringSchema({}),
|
|
11
|
-
new IntegerSchema({}),
|
|
12
|
-
])
|
|
9
|
+
const stringOrNumber = union([string(), integer()])
|
|
13
10
|
|
|
14
11
|
it('validates string input', () => {
|
|
15
12
|
const result = stringOrNumber.safeParse('hello')
|
|
@@ -47,14 +44,14 @@ describe('UnionSchema', () => {
|
|
|
47
44
|
})
|
|
48
45
|
|
|
49
46
|
describe('with object types', () => {
|
|
50
|
-
const schema =
|
|
51
|
-
|
|
52
|
-
type:
|
|
53
|
-
name:
|
|
47
|
+
const schema = union([
|
|
48
|
+
object({
|
|
49
|
+
type: string(),
|
|
50
|
+
name: string(),
|
|
54
51
|
}),
|
|
55
|
-
|
|
56
|
-
type:
|
|
57
|
-
age:
|
|
52
|
+
object({
|
|
53
|
+
type: string(),
|
|
54
|
+
age: integer(),
|
|
58
55
|
}),
|
|
59
56
|
])
|
|
60
57
|
|
|
@@ -91,11 +88,7 @@ describe('UnionSchema', () => {
|
|
|
91
88
|
})
|
|
92
89
|
|
|
93
90
|
describe('with three types', () => {
|
|
94
|
-
const schema =
|
|
95
|
-
new StringSchema({}),
|
|
96
|
-
new IntegerSchema({}),
|
|
97
|
-
new BooleanSchema({}),
|
|
98
|
-
])
|
|
91
|
+
const schema = union([string(), integer(), boolean()])
|
|
99
92
|
|
|
100
93
|
it('validates string input', () => {
|
|
101
94
|
const result = schema.safeParse('text')
|
|
@@ -124,10 +117,7 @@ describe('UnionSchema', () => {
|
|
|
124
117
|
})
|
|
125
118
|
|
|
126
119
|
describe('with constrained types', () => {
|
|
127
|
-
const schema =
|
|
128
|
-
new StringSchema({ minLength: 5 }),
|
|
129
|
-
new IntegerSchema({ minimum: 100 }),
|
|
130
|
-
])
|
|
120
|
+
const schema = union([string({ minLength: 5 }), integer({ minimum: 100 })])
|
|
131
121
|
|
|
132
122
|
it('validates string meeting constraint', () => {
|
|
133
123
|
const result = schema.safeParse('hello')
|
|
@@ -157,13 +147,13 @@ describe('UnionSchema', () => {
|
|
|
157
147
|
|
|
158
148
|
describe('edge cases', () => {
|
|
159
149
|
it('validates with single type in union', () => {
|
|
160
|
-
const schema =
|
|
150
|
+
const schema = union([string()])
|
|
161
151
|
const result = schema.safeParse('test')
|
|
162
152
|
expect(result.success).toBe(true)
|
|
163
153
|
})
|
|
164
154
|
|
|
165
155
|
it('rejects when single type in union does not match', () => {
|
|
166
|
-
const schema =
|
|
156
|
+
const schema = union([string()])
|
|
167
157
|
const result = schema.safeParse(123)
|
|
168
158
|
expect(result.success).toBe(false)
|
|
169
159
|
})
|
package/src/schema/union.ts
CHANGED
|
@@ -1,38 +1,42 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
InferInput,
|
|
3
|
+
InferOutput,
|
|
3
4
|
Schema,
|
|
5
|
+
ValidationContext,
|
|
4
6
|
ValidationError,
|
|
5
7
|
ValidationFailure,
|
|
6
|
-
ValidationResult,
|
|
7
8
|
Validator,
|
|
8
|
-
ValidatorContext,
|
|
9
9
|
} from '../core.js'
|
|
10
10
|
|
|
11
11
|
export type UnionSchemaValidators = readonly [Validator, ...Validator[]]
|
|
12
|
-
export type UnionSchemaOutput<V extends readonly Validator[]> = Infer<V[number]>
|
|
13
12
|
|
|
14
|
-
export class UnionSchema<
|
|
15
|
-
|
|
13
|
+
export class UnionSchema<
|
|
14
|
+
const TValidators extends UnionSchemaValidators = any,
|
|
15
|
+
> extends Schema<
|
|
16
|
+
InferInput<TValidators[number]>,
|
|
17
|
+
InferOutput<TValidators[number]>
|
|
16
18
|
> {
|
|
17
|
-
constructor(protected readonly validators:
|
|
19
|
+
constructor(protected readonly validators: TValidators) {
|
|
18
20
|
super()
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
validateInContext(
|
|
22
|
-
input: unknown,
|
|
23
|
-
ctx: ValidatorContext,
|
|
24
|
-
): ValidationResult<UnionSchemaOutput<V>> {
|
|
23
|
+
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
25
24
|
const failures: ValidationFailure[] = []
|
|
26
25
|
|
|
27
26
|
for (const validator of this.validators) {
|
|
28
27
|
const result = ctx.validate(input, validator)
|
|
29
|
-
if (result.success)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
failures.push(result)
|
|
33
|
-
}
|
|
28
|
+
if (result.success) return result
|
|
29
|
+
|
|
30
|
+
failures.push(result)
|
|
34
31
|
}
|
|
35
32
|
|
|
36
33
|
return ctx.failure(ValidationError.fromFailures(failures))
|
|
37
34
|
}
|
|
38
35
|
}
|
|
36
|
+
|
|
37
|
+
/*@__NO_SIDE_EFFECTS__*/
|
|
38
|
+
export function union<const TValidators extends UnionSchemaValidators>(
|
|
39
|
+
validators: TValidators,
|
|
40
|
+
) {
|
|
41
|
+
return new UnionSchema<TValidators>(validators)
|
|
42
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import {
|
|
2
|
+
import { unknownObject } from './unknown-object.js'
|
|
3
3
|
|
|
4
4
|
describe('UnknownObjectSchema', () => {
|
|
5
5
|
describe('basic validation', () => {
|
|
6
|
-
const schema =
|
|
6
|
+
const schema = unknownObject()
|
|
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 = unknownObject()
|
|
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 = unknownObject()
|
|
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 = unknownObject()
|
|
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 = unknownObject()
|
|
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 = unknownObject()
|
|
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 = unknownObject()
|
|
542
542
|
|
|
543
543
|
it('preserves the original object reference', () => {
|
|
544
544
|
const input = { key: 'value', count: 42 }
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { LexMap, isLexMap } from '@atproto/lex-data'
|
|
2
|
-
import { Schema,
|
|
2
|
+
import { Schema, ValidationContext } from '../core.js'
|
|
3
|
+
import { memoizedOptions } from '../util/memoize.js'
|
|
3
4
|
|
|
4
|
-
export type
|
|
5
|
+
export type UnknownObject = LexMap
|
|
5
6
|
|
|
6
|
-
export class UnknownObjectSchema extends Schema<
|
|
7
|
-
validateInContext(
|
|
8
|
-
input: unknown,
|
|
9
|
-
ctx: ValidatorContext,
|
|
10
|
-
): ValidationResult<UnknownObjectOutput> {
|
|
7
|
+
export class UnknownObjectSchema extends Schema<UnknownObject> {
|
|
8
|
+
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
11
9
|
if (isLexMap(input)) {
|
|
12
10
|
return ctx.success(input)
|
|
13
11
|
}
|
|
@@ -15,3 +13,7 @@ export class UnknownObjectSchema extends Schema<UnknownObjectOutput> {
|
|
|
15
13
|
return ctx.issueInvalidType(input, 'unknown')
|
|
16
14
|
}
|
|
17
15
|
}
|
|
16
|
+
|
|
17
|
+
export const unknownObject = /*#__PURE__*/ memoizedOptions(function () {
|
|
18
|
+
return new UnknownObjectSchema()
|
|
19
|
+
})
|