@atproto/lex-schema 0.1.5 → 0.1.6
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 +14 -0
- package/dist/core/$type.d.ts +2 -2
- package/dist/core/$type.d.ts.map +1 -1
- package/dist/core/$type.js.map +1 -1
- package/dist/core/record-key.d.ts +1 -1
- package/dist/core/record-key.d.ts.map +1 -1
- package/dist/core/record-key.js.map +1 -1
- package/dist/core/schema.d.ts +3 -2
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +1 -1
- package/dist/core/schema.js.map +1 -1
- package/dist/core/standard-schema.d.ts +2 -2
- package/dist/core/standard-schema.d.ts.map +1 -1
- package/dist/core/standard-schema.js.map +1 -1
- package/dist/core/string-format.d.ts +2 -2
- package/dist/core/string-format.d.ts.map +1 -1
- package/dist/core/string-format.js.map +1 -1
- package/dist/core/validation-error.d.ts +1 -1
- package/dist/core/validation-error.d.ts.map +1 -1
- package/dist/core/validation-error.js +1 -1
- package/dist/core/validation-error.js.map +1 -1
- package/dist/core/validator.d.ts +1 -1
- package/dist/core/validator.d.ts.map +1 -1
- package/dist/core/validator.js +1 -1
- package/dist/core/validator.js.map +1 -1
- package/dist/helpers.d.ts +2 -2
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +2 -2
- package/dist/helpers.js.map +1 -1
- package/dist/schema/array.d.ts +1 -1
- package/dist/schema/array.d.ts.map +1 -1
- package/dist/schema/array.js +1 -1
- package/dist/schema/array.js.map +1 -1
- package/dist/schema/blob.d.ts +1 -1
- package/dist/schema/blob.d.ts.map +1 -1
- package/dist/schema/blob.js +2 -2
- package/dist/schema/blob.js.map +1 -1
- package/dist/schema/boolean.js +1 -1
- package/dist/schema/boolean.js.map +1 -1
- package/dist/schema/bytes.js +1 -1
- package/dist/schema/bytes.js.map +1 -1
- package/dist/schema/cid.d.ts +1 -1
- package/dist/schema/cid.d.ts.map +1 -1
- package/dist/schema/cid.js +3 -3
- package/dist/schema/cid.js.map +1 -1
- package/dist/schema/custom.js +1 -1
- package/dist/schema/custom.js.map +1 -1
- package/dist/schema/dict.d.ts +1 -1
- 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 +1 -1
- package/dist/schema/discriminated-union.d.ts.map +1 -1
- package/dist/schema/discriminated-union.js +2 -1
- package/dist/schema/discriminated-union.js.map +1 -1
- package/dist/schema/enum.js +1 -1
- package/dist/schema/enum.js.map +1 -1
- package/dist/schema/integer.js +1 -1
- package/dist/schema/integer.js.map +1 -1
- package/dist/schema/intersection.d.ts +1 -1
- package/dist/schema/intersection.d.ts.map +1 -1
- package/dist/schema/intersection.js +3 -1
- package/dist/schema/intersection.js.map +1 -1
- package/dist/schema/lex-map.d.ts +1 -1
- package/dist/schema/lex-map.d.ts.map +1 -1
- package/dist/schema/lex-map.js +1 -1
- package/dist/schema/lex-map.js.map +1 -1
- package/dist/schema/lex-value.d.ts +1 -1
- package/dist/schema/lex-value.d.ts.map +1 -1
- package/dist/schema/lex-value.js +1 -1
- package/dist/schema/lex-value.js.map +1 -1
- package/dist/schema/literal.js +1 -1
- package/dist/schema/literal.js.map +1 -1
- package/dist/schema/never.js +1 -1
- package/dist/schema/never.js.map +1 -1
- package/dist/schema/null.js +1 -1
- package/dist/schema/null.js.map +1 -1
- package/dist/schema/nullable.d.ts +1 -1
- package/dist/schema/nullable.d.ts.map +1 -1
- package/dist/schema/nullable.js +1 -1
- package/dist/schema/nullable.js.map +1 -1
- package/dist/schema/object.d.ts +2 -1
- package/dist/schema/object.d.ts.map +1 -1
- package/dist/schema/object.js +1 -1
- package/dist/schema/object.js.map +1 -1
- package/dist/schema/optional.d.ts +2 -1
- package/dist/schema/optional.d.ts.map +1 -1
- package/dist/schema/optional.js +2 -1
- package/dist/schema/optional.js.map +1 -1
- package/dist/schema/params.d.ts +1 -1
- package/dist/schema/params.d.ts.map +1 -1
- package/dist/schema/params.js +1 -1
- package/dist/schema/params.js.map +1 -1
- package/dist/schema/payload.d.ts +3 -2
- package/dist/schema/payload.d.ts.map +1 -1
- package/dist/schema/payload.js +2 -1
- package/dist/schema/payload.js.map +1 -1
- package/dist/schema/permission-set.d.ts +1 -1
- package/dist/schema/permission-set.d.ts.map +1 -1
- package/dist/schema/permission-set.js +1 -0
- package/dist/schema/permission-set.js.map +1 -1
- package/dist/schema/permission.d.ts +1 -1
- package/dist/schema/permission.d.ts.map +1 -1
- package/dist/schema/permission.js.map +1 -1
- package/dist/schema/procedure.d.ts +1 -1
- package/dist/schema/procedure.d.ts.map +1 -1
- package/dist/schema/procedure.js +2 -0
- package/dist/schema/procedure.js.map +1 -1
- package/dist/schema/query.d.ts +1 -1
- package/dist/schema/query.d.ts.map +1 -1
- package/dist/schema/query.js +2 -0
- package/dist/schema/query.js.map +1 -1
- package/dist/schema/record.d.ts +2 -2
- package/dist/schema/record.d.ts.map +1 -1
- package/dist/schema/record.js +1 -1
- package/dist/schema/record.js.map +1 -1
- package/dist/schema/ref.d.ts +1 -1
- package/dist/schema/ref.d.ts.map +1 -1
- package/dist/schema/ref.js +1 -1
- package/dist/schema/ref.js.map +1 -1
- package/dist/schema/refine.d.ts +2 -2
- package/dist/schema/refine.d.ts.map +1 -1
- package/dist/schema/refine.js +1 -1
- package/dist/schema/refine.js.map +1 -1
- package/dist/schema/regexp.js +1 -1
- package/dist/schema/regexp.js.map +1 -1
- package/dist/schema/string.d.ts +2 -2
- package/dist/schema/string.d.ts.map +1 -1
- package/dist/schema/string.js +1 -1
- package/dist/schema/string.js.map +1 -1
- package/dist/schema/subscription.d.ts +3 -2
- package/dist/schema/subscription.d.ts.map +1 -1
- package/dist/schema/subscription.js +2 -0
- package/dist/schema/subscription.js.map +1 -1
- package/dist/schema/token.d.ts +1 -1
- package/dist/schema/token.d.ts.map +1 -1
- package/dist/schema/token.js +1 -1
- package/dist/schema/token.js.map +1 -1
- package/dist/schema/typed-object.d.ts +2 -2
- package/dist/schema/typed-object.d.ts.map +1 -1
- package/dist/schema/typed-object.js +1 -1
- package/dist/schema/typed-object.js.map +1 -1
- package/dist/schema/typed-ref.d.ts +1 -1
- 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 +1 -1
- package/dist/schema/typed-union.d.ts.map +1 -1
- package/dist/schema/typed-union.js +3 -1
- package/dist/schema/typed-union.js.map +1 -1
- package/dist/schema/union.d.ts +1 -1
- 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.js +1 -1
- package/dist/schema/unknown.js.map +1 -1
- package/dist/schema/with-default.d.ts +1 -1
- package/dist/schema/with-default.d.ts.map +1 -1
- package/dist/schema/with-default.js +1 -1
- package/dist/schema/with-default.js.map +1 -1
- package/package.json +6 -10
- package/src/core/$type.test.ts +0 -24
- package/src/core/$type.ts +0 -199
- package/src/core/record-key.ts +0 -85
- package/src/core/result.ts +0 -15
- package/src/core/schema.ts +0 -412
- package/src/core/standard-schema.test.ts +0 -124
- package/src/core/standard-schema.ts +0 -31
- package/src/core/string-format.ts +0 -411
- package/src/core/types.ts +0 -120
- package/src/core/validation-error.ts +0 -134
- package/src/core/validation-issue.ts +0 -340
- package/src/core/validator.ts +0 -636
- package/src/core.ts +0 -9
- package/src/external.ts +0 -3
- package/src/helpers.test.ts +0 -694
- package/src/helpers.ts +0 -222
- package/src/index.ts +0 -3
- package/src/schema/array.test.ts +0 -251
- package/src/schema/array.ts +0 -126
- package/src/schema/blob.test.ts +0 -733
- package/src/schema/blob.ts +0 -150
- package/src/schema/boolean.test.ts +0 -118
- package/src/schema/boolean.ts +0 -46
- package/src/schema/bytes.test.ts +0 -227
- package/src/schema/bytes.ts +0 -81
- package/src/schema/cid.test.ts +0 -125
- package/src/schema/cid.ts +0 -69
- package/src/schema/custom.test.ts +0 -414
- package/src/schema/custom.ts +0 -106
- package/src/schema/dict.test.ts +0 -181
- package/src/schema/dict.ts +0 -122
- package/src/schema/discriminated-union.test.ts +0 -676
- package/src/schema/discriminated-union.ts +0 -196
- package/src/schema/enum.test.ts +0 -398
- package/src/schema/enum.ts +0 -77
- package/src/schema/integer.test.ts +0 -314
- package/src/schema/integer.ts +0 -86
- package/src/schema/intersection.test.ts +0 -33
- package/src/schema/intersection.ts +0 -113
- package/src/schema/lex-map.test.ts +0 -593
- package/src/schema/lex-map.ts +0 -63
- package/src/schema/lex-value.test.ts +0 -81
- package/src/schema/lex-value.ts +0 -86
- package/src/schema/literal.test.ts +0 -533
- package/src/schema/literal.ts +0 -70
- package/src/schema/never.test.ts +0 -175
- package/src/schema/never.ts +0 -56
- package/src/schema/null.test.ts +0 -80
- package/src/schema/null.ts +0 -49
- package/src/schema/nullable.test.ts +0 -470
- package/src/schema/nullable.ts +0 -74
- package/src/schema/object.test.ts +0 -69
- package/src/schema/object.ts +0 -136
- package/src/schema/optional.test.ts +0 -479
- package/src/schema/optional.ts +0 -92
- package/src/schema/params.test.ts +0 -1118
- package/src/schema/params.ts +0 -371
- package/src/schema/payload.test.ts +0 -340
- package/src/schema/payload.ts +0 -204
- package/src/schema/permission-set.test.ts +0 -613
- package/src/schema/permission-set.ts +0 -86
- package/src/schema/permission.test.ts +0 -537
- package/src/schema/permission.ts +0 -63
- package/src/schema/procedure.test.ts +0 -324
- package/src/schema/procedure.ts +0 -98
- package/src/schema/query.test.ts +0 -348
- package/src/schema/query.ts +0 -86
- package/src/schema/record.test.ts +0 -812
- package/src/schema/record.ts +0 -217
- package/src/schema/ref.test.ts +0 -349
- package/src/schema/ref.ts +0 -103
- package/src/schema/refine.test.ts +0 -579
- package/src/schema/refine.ts +0 -153
- package/src/schema/regexp.test.ts +0 -577
- package/src/schema/regexp.ts +0 -82
- package/src/schema/string.test.ts +0 -773
- package/src/schema/string.ts +0 -229
- package/src/schema/subscription.test.ts +0 -499
- package/src/schema/subscription.ts +0 -108
- package/src/schema/token.test.ts +0 -152
- package/src/schema/token.ts +0 -103
- package/src/schema/typed-object.test.ts +0 -745
- package/src/schema/typed-object.ts +0 -181
- package/src/schema/typed-ref.test.ts +0 -796
- package/src/schema/typed-ref.ts +0 -126
- package/src/schema/typed-union.test.ts +0 -355
- package/src/schema/typed-union.ts +0 -130
- package/src/schema/union.test.ts +0 -191
- package/src/schema/union.ts +0 -89
- package/src/schema/unknown.test.ts +0 -313
- package/src/schema/unknown.ts +0 -47
- package/src/schema/with-default.ts +0 -81
- package/src/schema.ts +0 -43
- package/src/util/array-agg.test.ts +0 -42
- package/src/util/array-agg.ts +0 -44
- package/src/util/assertion-util.ts +0 -1
- package/src/util/if-any.ts +0 -3
- package/src/util/lazy-property.ts +0 -14
- package/src/util/memoize.ts +0 -37
- package/tsconfig.build.json +0 -12
- package/tsconfig.json +0 -7
- package/tsconfig.tests.json +0 -8
package/src/schema/record.ts
DELETED
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
import { LexMap } from '@atproto/lex-data'
|
|
2
|
-
import {
|
|
3
|
-
$Typed,
|
|
4
|
-
$typed,
|
|
5
|
-
InferInput,
|
|
6
|
-
InferOutput,
|
|
7
|
-
LexiconRecordKey,
|
|
8
|
-
NsidString,
|
|
9
|
-
RecordKeyValue,
|
|
10
|
-
Schema,
|
|
11
|
-
Unknown$TypedObject,
|
|
12
|
-
ValidationContext,
|
|
13
|
-
Validator,
|
|
14
|
-
} from '../core.js'
|
|
15
|
-
import { lazyProperty } from '../util/lazy-property.js'
|
|
16
|
-
import { literal } from './literal.js'
|
|
17
|
-
import { string } from './string.js'
|
|
18
|
-
import { withDefault } from './with-default.js'
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Infers the record key type from a RecordSchema.
|
|
22
|
-
*
|
|
23
|
-
* @template R - The RecordSchema type
|
|
24
|
-
*/
|
|
25
|
-
export type InferRecordKey<R extends RecordSchema> =
|
|
26
|
-
R extends RecordSchema<infer TKey> ? RecordKeyValue<TKey> : never
|
|
27
|
-
|
|
28
|
-
export type TypedRecord<
|
|
29
|
-
TType extends NsidString,
|
|
30
|
-
TValue extends { $type?: unknown } = { $type?: unknown },
|
|
31
|
-
> = TValue extends { $type: TType }
|
|
32
|
-
? TValue
|
|
33
|
-
: $Typed<Exclude<TValue, Unknown$TypedObject>, TType>
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Schema for AT Protocol records with a type identifier and key constraints.
|
|
37
|
-
*
|
|
38
|
-
* Records are the primary data unit in AT Protocol. Each record has a `$type`
|
|
39
|
-
* field identifying its Lexicon schema, and is stored at a specific key
|
|
40
|
-
* (TID, NSID, or other format) in a repository.
|
|
41
|
-
*
|
|
42
|
-
* @template TKey - The record key type ('tid', 'nsid', 'any', or 'literal:...')
|
|
43
|
-
* @template TType - The NSID string identifying this record type
|
|
44
|
-
* @template TShape - The validator type for the record's data shape
|
|
45
|
-
*
|
|
46
|
-
* @example
|
|
47
|
-
* ```ts
|
|
48
|
-
* const postSchema = new RecordSchema(
|
|
49
|
-
* 'tid',
|
|
50
|
-
* 'app.bsky.feed.post',
|
|
51
|
-
* l.object({ text: l.string(), createdAt: l.string() })
|
|
52
|
-
* )
|
|
53
|
-
* ```
|
|
54
|
-
*/
|
|
55
|
-
export class RecordSchema<
|
|
56
|
-
const TKey extends LexiconRecordKey = LexiconRecordKey,
|
|
57
|
-
const TType extends NsidString = NsidString,
|
|
58
|
-
const TShape extends Validator<LexMap> = Validator<LexMap>,
|
|
59
|
-
> extends Schema<
|
|
60
|
-
$Typed<InferInput<TShape>, TType>,
|
|
61
|
-
$Typed<InferOutput<TShape>, TType>
|
|
62
|
-
> {
|
|
63
|
-
readonly type = 'record' as const
|
|
64
|
-
|
|
65
|
-
keySchema: RecordKeySchema<TKey>
|
|
66
|
-
|
|
67
|
-
constructor(
|
|
68
|
-
readonly key: TKey,
|
|
69
|
-
readonly $type: TType,
|
|
70
|
-
readonly schema: TShape,
|
|
71
|
-
) {
|
|
72
|
-
super()
|
|
73
|
-
this.keySchema = recordKey(key)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
77
|
-
const result = ctx.validate(input, this.schema)
|
|
78
|
-
|
|
79
|
-
if (!result.success) {
|
|
80
|
-
return result
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (result.value.$type !== this.$type) {
|
|
84
|
-
return ctx.issueInvalidPropertyValue(result.value, '$type', [this.$type])
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return result
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
build(
|
|
91
|
-
input: Omit<InferOutput<TShape>, '$type'>,
|
|
92
|
-
): $Typed<InferOutput<TShape>, TType>
|
|
93
|
-
build(
|
|
94
|
-
input: Omit<InferInput<TShape>, '$type'>,
|
|
95
|
-
): $Typed<InferInput<TShape>, TType>
|
|
96
|
-
build(input: Record<string, unknown>) {
|
|
97
|
-
return $typed(input, this.$type)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
isTypeOf<TValue extends { $type?: unknown }>(
|
|
101
|
-
value: TValue,
|
|
102
|
-
): value is TypedRecord<TType, TValue> {
|
|
103
|
-
return value.$type === this.$type
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Bound alias for {@link build} for compatibility with generated utilities.
|
|
108
|
-
* @see {@link build}
|
|
109
|
-
*/
|
|
110
|
-
get $build(): typeof this.build {
|
|
111
|
-
return lazyProperty(this, '$build', this.build.bind(this))
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Bound alias for {@link isTypeOf} for compatibility with generated utilities.
|
|
116
|
-
* @see {@link isTypeOf}
|
|
117
|
-
*/
|
|
118
|
-
get $isTypeOf(): typeof this.isTypeOf {
|
|
119
|
-
return lazyProperty(this, '$isTypeOf', this.isTypeOf.bind(this))
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export type RecordKeySchemaOutput<Key extends LexiconRecordKey> =
|
|
124
|
-
RecordKeyValue<Key>
|
|
125
|
-
|
|
126
|
-
export type RecordKeySchema<Key extends LexiconRecordKey> = Schema<
|
|
127
|
-
RecordKeyValue<Key>
|
|
128
|
-
>
|
|
129
|
-
|
|
130
|
-
const keySchema = string({ format: 'record-key' })
|
|
131
|
-
const tidSchema = string({ format: 'tid' })
|
|
132
|
-
const nsidSchema = string({ format: 'nsid' })
|
|
133
|
-
const selfLiteralSchema = withDefault(literal('self'), 'self')
|
|
134
|
-
|
|
135
|
-
function recordKey<Key extends LexiconRecordKey>(
|
|
136
|
-
key: Key,
|
|
137
|
-
): RecordKeySchema<Key> {
|
|
138
|
-
// @NOTE Use cached instances for common schemas
|
|
139
|
-
if (key === 'any') return keySchema as any
|
|
140
|
-
if (key === 'tid') return tidSchema as any
|
|
141
|
-
if (key === 'nsid') return nsidSchema as any
|
|
142
|
-
if (key.startsWith('literal:')) {
|
|
143
|
-
const value = key.slice(8) as RecordKeyValue<Key>
|
|
144
|
-
if (value === 'self') return selfLiteralSchema as any
|
|
145
|
-
return withDefault(literal(value), value)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
throw new Error(`Unsupported record key type: ${key}`)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Ensures that a `$type` used in a record is a valid NSID (i.e. no fragment).
|
|
153
|
-
*/
|
|
154
|
-
type AsNsid<T> = T extends `${string}#${string}` ? never : T
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Creates a record schema for AT Protocol records.
|
|
158
|
-
*
|
|
159
|
-
* Records are the primary data unit in AT Protocol repositories. They have
|
|
160
|
-
* a `$type` field identifying their Lexicon schema, and are stored at keys
|
|
161
|
-
* following a specific format (TID, NSID, etc.).
|
|
162
|
-
*
|
|
163
|
-
* This function offers two overloads:
|
|
164
|
-
* - One that infers the output type from the provided arguments (does not
|
|
165
|
-
* support circular references)
|
|
166
|
-
* - One with an explicitly defined interface for use with codegen and
|
|
167
|
-
* circular references
|
|
168
|
-
*
|
|
169
|
-
* @param key - The record key type: 'tid', 'nsid', 'any', or 'literal:value'
|
|
170
|
-
* @param type - The NSID identifying this record type (e.g., 'app.bsky.feed.post')
|
|
171
|
-
* @param validator - Schema validator for the record's properties
|
|
172
|
-
* @returns A new {@link RecordSchema} instance
|
|
173
|
-
*
|
|
174
|
-
* @example
|
|
175
|
-
* ```ts
|
|
176
|
-
* // Post record with TID key
|
|
177
|
-
* const postSchema = l.record('tid', 'app.bsky.feed.post', l.object({
|
|
178
|
-
* text: l.string({ maxGraphemes: 300 }),
|
|
179
|
-
* createdAt: l.string({ format: 'datetime' }),
|
|
180
|
-
* reply: l.optional(l.object({
|
|
181
|
-
* root: l.ref(() => strongRefSchema),
|
|
182
|
-
* parent: l.ref(() => strongRefSchema),
|
|
183
|
-
* })),
|
|
184
|
-
* }))
|
|
185
|
-
*
|
|
186
|
-
* // Profile record with literal 'self' key
|
|
187
|
-
* const profileSchema = l.record('literal:self', 'app.bsky.actor.profile', l.object({
|
|
188
|
-
* displayName: l.optional(l.string({ maxGraphemes: 64 })),
|
|
189
|
-
* description: l.optional(l.string({ maxGraphemes: 256 })),
|
|
190
|
-
* avatar: l.optional(l.blob({ accept: ['image/*'] })),
|
|
191
|
-
* }))
|
|
192
|
-
*
|
|
193
|
-
* // Build a record with automatic $type injection
|
|
194
|
-
* const post = postSchema.build({ text: 'Hello!', createdAt: new Date().toISOString() })
|
|
195
|
-
* ```
|
|
196
|
-
*/
|
|
197
|
-
export function record<
|
|
198
|
-
const K extends LexiconRecordKey,
|
|
199
|
-
const T extends NsidString,
|
|
200
|
-
const S extends Validator<LexMap>,
|
|
201
|
-
>(key: K, type: AsNsid<T>, validator: S): RecordSchema<K, T, S>
|
|
202
|
-
export function record<
|
|
203
|
-
const K extends LexiconRecordKey,
|
|
204
|
-
const V extends LexMap & { $type: NsidString },
|
|
205
|
-
>(
|
|
206
|
-
key: K,
|
|
207
|
-
type: AsNsid<V['$type']>,
|
|
208
|
-
validator: Validator<Omit<V, '$type'>>,
|
|
209
|
-
): RecordSchema<K, V['$type'], Validator<Omit<V, '$type'>>>
|
|
210
|
-
/*@__NO_SIDE_EFFECTS__*/
|
|
211
|
-
export function record<
|
|
212
|
-
const K extends LexiconRecordKey,
|
|
213
|
-
const T extends NsidString,
|
|
214
|
-
const S extends Validator<LexMap>,
|
|
215
|
-
>(key: K, type: T, validator: S) {
|
|
216
|
-
return new RecordSchema<K, T, S>(key, type, validator)
|
|
217
|
-
}
|
package/src/schema/ref.test.ts
DELETED
|
@@ -1,349 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { Schema, Validator } from '../core.js'
|
|
3
|
-
import { integer } from './integer.js'
|
|
4
|
-
import { object } from './object.js'
|
|
5
|
-
import { optional } from './optional.js'
|
|
6
|
-
import { ref } from './ref.js'
|
|
7
|
-
import { string } from './string.js'
|
|
8
|
-
|
|
9
|
-
describe('RefSchema', () => {
|
|
10
|
-
describe('basic validation', () => {
|
|
11
|
-
it('validates through a simple string reference', () => {
|
|
12
|
-
const schema = ref(() => innerSchema)
|
|
13
|
-
const innerSchema = string()
|
|
14
|
-
const result = schema.safeParse('hello')
|
|
15
|
-
expect(result.success).toBe(true)
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it('validates through an integer reference', () => {
|
|
19
|
-
const schema = ref(() => innerSchema)
|
|
20
|
-
const innerSchema = integer()
|
|
21
|
-
const result = schema.safeParse(42)
|
|
22
|
-
expect(result.success).toBe(true)
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it('rejects invalid input through reference', () => {
|
|
26
|
-
const schema = ref(() => innerSchema)
|
|
27
|
-
const innerSchema = string()
|
|
28
|
-
const result = schema.safeParse(123)
|
|
29
|
-
expect(result.success).toBe(false)
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it('validates null rejection through reference', () => {
|
|
33
|
-
const schema = ref(() => innerSchema)
|
|
34
|
-
const innerSchema = string()
|
|
35
|
-
const result = schema.safeParse(null)
|
|
36
|
-
expect(result.success).toBe(false)
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it('validates undefined rejection through reference', () => {
|
|
40
|
-
const schema = ref(() => innerSchema)
|
|
41
|
-
const innerSchema = integer()
|
|
42
|
-
const result = schema.safeParse(undefined)
|
|
43
|
-
expect(result.success).toBe(false)
|
|
44
|
-
})
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
describe('lazy schema resolution', () => {
|
|
48
|
-
it('does not call getter until first validation', () => {
|
|
49
|
-
let getterCalled = false
|
|
50
|
-
const schema = ref(() => {
|
|
51
|
-
getterCalled = true
|
|
52
|
-
return string()
|
|
53
|
-
})
|
|
54
|
-
expect(getterCalled).toBe(false)
|
|
55
|
-
|
|
56
|
-
schema.safeParse('test')
|
|
57
|
-
expect(getterCalled).toBe(true)
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('throws error if getter is called multiple times', () => {
|
|
61
|
-
const schema = ref(() => innerSchema)
|
|
62
|
-
const innerSchema = string()
|
|
63
|
-
|
|
64
|
-
// Access schema property to resolve it
|
|
65
|
-
void schema.validator
|
|
66
|
-
|
|
67
|
-
// Try to access the original getter again (which should throw)
|
|
68
|
-
// This is internal behavior, but we're testing the protection mechanism
|
|
69
|
-
expect(() => {
|
|
70
|
-
// Force access to the cached schema property
|
|
71
|
-
const schemaValue = schema.validator
|
|
72
|
-
// This should work fine as it's now cached
|
|
73
|
-
expect(schemaValue).toBeDefined()
|
|
74
|
-
}).not.toThrow()
|
|
75
|
-
})
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
describe('with object schemas', () => {
|
|
79
|
-
it('validates objects through reference', () => {
|
|
80
|
-
const schema = ref(() => innerSchema)
|
|
81
|
-
const innerSchema = object({
|
|
82
|
-
name: string(),
|
|
83
|
-
age: integer(),
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
expect(
|
|
87
|
-
schema.safeValidate({
|
|
88
|
-
name: 'Alice',
|
|
89
|
-
age: 30,
|
|
90
|
-
}),
|
|
91
|
-
).toMatchObject({
|
|
92
|
-
success: true,
|
|
93
|
-
})
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
it('rejects invalid objects through reference', () => {
|
|
97
|
-
const schema = ref(() => innerSchema)
|
|
98
|
-
const innerSchema = object({
|
|
99
|
-
name: string(),
|
|
100
|
-
age: integer(),
|
|
101
|
-
})
|
|
102
|
-
const result = schema.safeParse({
|
|
103
|
-
name: 'Alice',
|
|
104
|
-
age: 'thirty',
|
|
105
|
-
})
|
|
106
|
-
expect(result.success).toBe(false)
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
it('rejects objects with missing properties through reference', () => {
|
|
110
|
-
const schema = ref(() => innerSchema)
|
|
111
|
-
const innerSchema = object({
|
|
112
|
-
name: string(),
|
|
113
|
-
age: integer(),
|
|
114
|
-
})
|
|
115
|
-
const result = schema.safeParse({
|
|
116
|
-
name: 'Alice',
|
|
117
|
-
})
|
|
118
|
-
expect(result.success).toBe(false)
|
|
119
|
-
})
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
describe('with constrained schemas', () => {
|
|
123
|
-
it('validates string with minLength constraint through reference', () => {
|
|
124
|
-
const schema = ref(() => innerSchema)
|
|
125
|
-
const innerSchema = string({ minLength: 5 })
|
|
126
|
-
const result = schema.safeParse('hello')
|
|
127
|
-
expect(result.success).toBe(true)
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
it('rejects string violating minLength through reference', () => {
|
|
131
|
-
const schema = ref(() => innerSchema)
|
|
132
|
-
const innerSchema = string({ minLength: 5 })
|
|
133
|
-
const result = schema.safeParse('hi')
|
|
134
|
-
expect(result.success).toBe(false)
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
it('validates integer with range constraints through reference', () => {
|
|
138
|
-
const schema = ref(() => innerSchema)
|
|
139
|
-
const innerSchema = integer({ minimum: 0, maximum: 100 })
|
|
140
|
-
const result = schema.safeParse(50)
|
|
141
|
-
expect(result.success).toBe(true)
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
it('rejects integer violating constraints through reference', () => {
|
|
145
|
-
const schema = ref(() => innerSchema)
|
|
146
|
-
const innerSchema = integer({ minimum: 0, maximum: 100 })
|
|
147
|
-
const result = schema.safeParse(150)
|
|
148
|
-
expect(result.success).toBe(false)
|
|
149
|
-
})
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
describe('circular references', () => {
|
|
153
|
-
it('supports indirect circular references', () => {
|
|
154
|
-
// Create two schemas that reference each other
|
|
155
|
-
// This demonstrates forward references are possible
|
|
156
|
-
|
|
157
|
-
type A = { value: string; ref?: B }
|
|
158
|
-
type B = { value: number; ref?: A }
|
|
159
|
-
|
|
160
|
-
const schemaA: Schema<A> = object({
|
|
161
|
-
value: string(),
|
|
162
|
-
ref: optional(ref<Validator<B>>((() => schemaB) as any)),
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
const schemaB: Schema<B> = object({
|
|
166
|
-
value: integer(),
|
|
167
|
-
ref: optional(ref<Validator<A>>((() => schemaA) as any)),
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
expect(
|
|
171
|
-
schemaB.matches({
|
|
172
|
-
value: 42,
|
|
173
|
-
ref: {
|
|
174
|
-
value: 'hello',
|
|
175
|
-
ref: {
|
|
176
|
-
value: 3,
|
|
177
|
-
},
|
|
178
|
-
},
|
|
179
|
-
}),
|
|
180
|
-
).toBe(true)
|
|
181
|
-
|
|
182
|
-
expect(
|
|
183
|
-
schemaA.matches({
|
|
184
|
-
value: 'hello',
|
|
185
|
-
ref: {
|
|
186
|
-
value: 3,
|
|
187
|
-
ref: {
|
|
188
|
-
value: 'world',
|
|
189
|
-
},
|
|
190
|
-
},
|
|
191
|
-
}),
|
|
192
|
-
).toBe(true)
|
|
193
|
-
})
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
describe('multiple validations', () => {
|
|
197
|
-
it('validates multiple inputs correctly', () => {
|
|
198
|
-
const schema = ref(() => innerSchema)
|
|
199
|
-
const innerSchema = string({ minLength: 3 })
|
|
200
|
-
|
|
201
|
-
const result1 = schema.safeParse('hello')
|
|
202
|
-
expect(result1.success).toBe(true)
|
|
203
|
-
|
|
204
|
-
const result2 = schema.safeParse('hi')
|
|
205
|
-
expect(result2.success).toBe(false)
|
|
206
|
-
|
|
207
|
-
const result3 = schema.safeParse('world')
|
|
208
|
-
expect(result3.success).toBe(true)
|
|
209
|
-
|
|
210
|
-
const result4 = schema.safeParse('no')
|
|
211
|
-
expect(result4.success).toBe(false)
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
it('handles different types of validation failures', () => {
|
|
215
|
-
const schema = ref(() =>
|
|
216
|
-
object({
|
|
217
|
-
name: string({ minLength: 2 }),
|
|
218
|
-
age: integer({ minimum: 0 }),
|
|
219
|
-
}),
|
|
220
|
-
)
|
|
221
|
-
|
|
222
|
-
const result1 = schema.safeParse({ name: 'A', age: 25 })
|
|
223
|
-
expect(result1.success).toBe(false)
|
|
224
|
-
|
|
225
|
-
const result2 = schema.safeParse({ name: 'Alice', age: -5 })
|
|
226
|
-
expect(result2.success).toBe(false)
|
|
227
|
-
|
|
228
|
-
const result3 = schema.safeParse({ name: 'Alice', age: 25 })
|
|
229
|
-
expect(result3.success).toBe(true)
|
|
230
|
-
})
|
|
231
|
-
})
|
|
232
|
-
|
|
233
|
-
describe('edge cases', () => {
|
|
234
|
-
it('handles empty string validation', () => {
|
|
235
|
-
const schema = ref(() => innerSchema)
|
|
236
|
-
const innerSchema = string()
|
|
237
|
-
const result = schema.safeParse('')
|
|
238
|
-
expect(result.success).toBe(true)
|
|
239
|
-
})
|
|
240
|
-
|
|
241
|
-
it('handles zero validation', () => {
|
|
242
|
-
const schema = ref(() => innerSchema)
|
|
243
|
-
const innerSchema = integer()
|
|
244
|
-
const result = schema.safeParse(0)
|
|
245
|
-
expect(result.success).toBe(true)
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
it('rejects NaN through integer reference', () => {
|
|
249
|
-
const schema = ref(() => innerSchema)
|
|
250
|
-
const innerSchema = integer()
|
|
251
|
-
const result = schema.safeParse(NaN)
|
|
252
|
-
expect(result.success).toBe(false)
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
it('rejects Infinity through integer reference', () => {
|
|
256
|
-
const schema = ref(() => innerSchema)
|
|
257
|
-
const innerSchema = integer()
|
|
258
|
-
const result = schema.safeParse(Infinity)
|
|
259
|
-
expect(result.success).toBe(false)
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
it('rejects arrays when expecting string', () => {
|
|
263
|
-
const schema = ref(() => innerSchema)
|
|
264
|
-
const innerSchema = string()
|
|
265
|
-
const result = schema.safeParse(['array'])
|
|
266
|
-
expect(result.success).toBe(false)
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
it('rejects objects when expecting string', () => {
|
|
270
|
-
const schema = ref(() => innerSchema)
|
|
271
|
-
const innerSchema = string()
|
|
272
|
-
const result = schema.safeParse({ key: 'value' })
|
|
273
|
-
expect(result.success).toBe(false)
|
|
274
|
-
})
|
|
275
|
-
|
|
276
|
-
it('rejects booleans when expecting string', () => {
|
|
277
|
-
const schema = ref(() => innerSchema)
|
|
278
|
-
const innerSchema = string()
|
|
279
|
-
const result = schema.safeParse(true)
|
|
280
|
-
expect(result.success).toBe(false)
|
|
281
|
-
})
|
|
282
|
-
})
|
|
283
|
-
|
|
284
|
-
describe('nested references', () => {
|
|
285
|
-
it('validates through nested RefSchema', () => {
|
|
286
|
-
const innerRef = ref(() => innerSchema)
|
|
287
|
-
const innerSchema = string({ minLength: 3 })
|
|
288
|
-
const outerRef = ref(() => innerRef)
|
|
289
|
-
|
|
290
|
-
const result = outerRef.safeParse('hello')
|
|
291
|
-
expect(result.success).toBe(true)
|
|
292
|
-
})
|
|
293
|
-
|
|
294
|
-
it('rejects invalid input through nested RefSchema', () => {
|
|
295
|
-
const innerRef = ref(() => innerSchema)
|
|
296
|
-
const innerSchema = string({ minLength: 3 })
|
|
297
|
-
const outerRef = ref(() => innerRef)
|
|
298
|
-
|
|
299
|
-
const result = outerRef.safeParse('hi')
|
|
300
|
-
expect(result.success).toBe(false)
|
|
301
|
-
})
|
|
302
|
-
|
|
303
|
-
it('validates with deeply nested references', () => {
|
|
304
|
-
const level3 = ref(() => innerSchema)
|
|
305
|
-
const innerSchema = integer({ minimum: 0 })
|
|
306
|
-
const level2 = ref(() => level3)
|
|
307
|
-
const level1 = ref(() => level2)
|
|
308
|
-
|
|
309
|
-
const result = level1.safeParse(42)
|
|
310
|
-
expect(result.success).toBe(true)
|
|
311
|
-
})
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
describe('schema property access', () => {
|
|
315
|
-
it('allows direct access to resolved schema', () => {
|
|
316
|
-
const innerSchema = string({ minLength: 5 })
|
|
317
|
-
const refSchema = ref(() => innerSchema)
|
|
318
|
-
|
|
319
|
-
const resolved = refSchema.validator
|
|
320
|
-
expect(resolved).toBe(innerSchema)
|
|
321
|
-
})
|
|
322
|
-
|
|
323
|
-
it('returns same instance on multiple schema property accesses', () => {
|
|
324
|
-
const innerSchema = string()
|
|
325
|
-
const refSchema = ref(() => innerSchema)
|
|
326
|
-
|
|
327
|
-
const first = refSchema.validator
|
|
328
|
-
const second = refSchema.validator
|
|
329
|
-
const third = refSchema.validator
|
|
330
|
-
|
|
331
|
-
expect(first).toBe(second)
|
|
332
|
-
expect(second).toBe(third)
|
|
333
|
-
})
|
|
334
|
-
|
|
335
|
-
it('resolves schema before validation', () => {
|
|
336
|
-
let resolved = false
|
|
337
|
-
const refSchema = ref(() => {
|
|
338
|
-
resolved = true
|
|
339
|
-
return string()
|
|
340
|
-
})
|
|
341
|
-
|
|
342
|
-
expect(resolved).toBe(false)
|
|
343
|
-
|
|
344
|
-
const schemaValue = refSchema.validator
|
|
345
|
-
expect(resolved).toBe(true)
|
|
346
|
-
expect(schemaValue).toBeDefined()
|
|
347
|
-
})
|
|
348
|
-
})
|
|
349
|
-
})
|
package/src/schema/ref.ts
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
InferInput,
|
|
3
|
-
InferOutput,
|
|
4
|
-
Schema,
|
|
5
|
-
ValidationContext,
|
|
6
|
-
Validator,
|
|
7
|
-
WrappedValidator,
|
|
8
|
-
} from '../core.js'
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Function type that returns a validator, used for lazy schema resolution.
|
|
12
|
-
*
|
|
13
|
-
* @template TValidator - The validator type that will be returned
|
|
14
|
-
*/
|
|
15
|
-
export type RefSchemaGetter<out TValidator extends Validator> = () => TValidator
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Schema for creating references to other schemas with lazy resolution.
|
|
19
|
-
*
|
|
20
|
-
* Useful for handling circular references or breaking module dependency cycles.
|
|
21
|
-
* The referenced schema is resolved lazily when first needed for validation.
|
|
22
|
-
*
|
|
23
|
-
* @template TValidator - The referenced validator type
|
|
24
|
-
*
|
|
25
|
-
* @example
|
|
26
|
-
* ```ts
|
|
27
|
-
* // Self-referential schema for tree structure
|
|
28
|
-
* const nodeSchema = l.object({
|
|
29
|
-
* value: l.string(),
|
|
30
|
-
* children: l.array(l.ref(() => nodeSchema)),
|
|
31
|
-
* })
|
|
32
|
-
* ```
|
|
33
|
-
*/
|
|
34
|
-
export class RefSchema<const TValidator extends Validator>
|
|
35
|
-
extends Schema<InferInput<TValidator>, InferOutput<TValidator>>
|
|
36
|
-
implements WrappedValidator<TValidator>
|
|
37
|
-
{
|
|
38
|
-
readonly type = 'ref' as const
|
|
39
|
-
|
|
40
|
-
#getter: RefSchemaGetter<TValidator>
|
|
41
|
-
|
|
42
|
-
constructor(getter: RefSchemaGetter<TValidator>) {
|
|
43
|
-
// @NOTE In order to avoid circular dependency issues, we don't resolve
|
|
44
|
-
// the schema here. Instead, we resolve it lazily when first accessed.
|
|
45
|
-
|
|
46
|
-
super()
|
|
47
|
-
|
|
48
|
-
this.#getter = getter
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
get validator(): TValidator {
|
|
52
|
-
return this.#getter.call(null)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
unwrap(): TValidator {
|
|
56
|
-
return this.validator
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
60
|
-
return ctx.validate(input, this.validator)
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Creates a reference schema with lazy resolution.
|
|
66
|
-
*
|
|
67
|
-
* Allows referencing schemas that may not be defined yet, enabling
|
|
68
|
-
* circular references and breaking dependency cycles. The getter function
|
|
69
|
-
* is called lazily when validation is first performed.
|
|
70
|
-
*
|
|
71
|
-
* @param get - Function that returns the referenced validator
|
|
72
|
-
* @returns A new {@link RefSchema} instance
|
|
73
|
-
*
|
|
74
|
-
* @example
|
|
75
|
-
* ```ts
|
|
76
|
-
* // Circular reference - tree node that contains children of the same type
|
|
77
|
-
* const treeNodeSchema = l.object({
|
|
78
|
-
* name: l.string(),
|
|
79
|
-
* children: l.optional(l.array(l.ref(() => treeNodeSchema))),
|
|
80
|
-
* })
|
|
81
|
-
*
|
|
82
|
-
* // Cross-module reference
|
|
83
|
-
* const commentSchema = l.object({
|
|
84
|
-
* text: l.string(),
|
|
85
|
-
* author: l.ref(() => userSchema), // userSchema defined elsewhere
|
|
86
|
-
* })
|
|
87
|
-
*
|
|
88
|
-
* // Explicitly typed reference
|
|
89
|
-
* const itemSchema = l.ref<Item>(() => complexItemSchema)
|
|
90
|
-
* ```
|
|
91
|
-
*/
|
|
92
|
-
/*@__NO_SIDE_EFFECTS__*/
|
|
93
|
-
export function ref<const TValidator extends Validator>(
|
|
94
|
-
get: RefSchemaGetter<TValidator>,
|
|
95
|
-
): RefSchema<TValidator>
|
|
96
|
-
export function ref<TInput, TOutput extends TInput = TInput>(
|
|
97
|
-
get: RefSchemaGetter<Validator<TInput, TOutput>>,
|
|
98
|
-
): RefSchema<Validator<TInput, TOutput>>
|
|
99
|
-
export function ref<const TValidator extends Validator>(
|
|
100
|
-
get: RefSchemaGetter<TValidator>,
|
|
101
|
-
) {
|
|
102
|
-
return new RefSchema<TValidator>(get)
|
|
103
|
-
}
|