@atproto/lex-schema 0.0.14 → 0.0.16
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 +30 -0
- package/dist/core/schema.d.ts +5 -4
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +7 -2
- package/dist/core/schema.js.map +1 -1
- package/dist/core/standard-schema.d.ts +14 -0
- package/dist/core/standard-schema.d.ts.map +1 -0
- package/dist/core/standard-schema.js +27 -0
- package/dist/core/standard-schema.js.map +1 -0
- package/dist/core/string-format.d.ts +24 -17
- package/dist/core/string-format.d.ts.map +1 -1
- package/dist/core/string-format.js +57 -30
- package/dist/core/string-format.js.map +1 -1
- package/dist/core/validation-error.d.ts +10 -2
- package/dist/core/validation-error.d.ts.map +1 -1
- package/dist/core/validation-error.js +10 -0
- package/dist/core/validation-error.js.map +1 -1
- package/dist/core/validation-issue.d.ts +15 -15
- package/dist/core/validation-issue.d.ts.map +1 -1
- package/dist/core/validation-issue.js +33 -29
- package/dist/core/validation-issue.js.map +1 -1
- package/dist/core/validator.d.ts +29 -14
- package/dist/core/validator.d.ts.map +1 -1
- package/dist/core/validator.js +4 -2
- package/dist/core/validator.js.map +1 -1
- package/dist/core.d.ts +0 -1
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +0 -1
- package/dist/core.js.map +1 -1
- package/dist/schema/blob.d.ts +10 -8
- package/dist/schema/blob.d.ts.map +1 -1
- package/dist/schema/blob.js +39 -14
- package/dist/schema/blob.js.map +1 -1
- package/dist/schema/custom.d.ts +1 -1
- package/dist/schema/custom.d.ts.map +1 -1
- package/dist/schema/custom.js.map +1 -1
- package/dist/schema/never.d.ts +1 -1
- package/dist/schema/nullable.d.ts +1 -1
- package/dist/schema/payload.d.ts +2 -2
- package/dist/schema/payload.d.ts.map +1 -1
- package/dist/schema/payload.js.map +1 -1
- package/dist/schema/record.d.ts +1 -1
- package/dist/schema/ref.d.ts +1 -1
- package/dist/schema/ref.d.ts.map +1 -1
- package/dist/schema/ref.js.map +1 -1
- package/dist/schema/refine.d.ts +1 -1
- package/dist/schema/refine.d.ts.map +1 -1
- package/dist/schema/refine.js.map +1 -1
- package/dist/schema/string.js +1 -1
- package/dist/schema/string.js.map +1 -1
- package/dist/schema/typed-ref.d.ts +1 -1
- package/dist/schema/typed-union.d.ts +1 -1
- package/dist/schema/union.d.ts +2 -2
- package/dist/schema/union.d.ts.map +1 -1
- package/package.json +5 -3
- package/src/core/schema.ts +12 -11
- package/src/core/standard-schema.test.ts +124 -0
- package/src/core/standard-schema.ts +31 -0
- package/src/core/string-format.ts +73 -31
- package/src/core/validation-error.ts +16 -1
- package/src/core/validation-issue.ts +32 -32
- package/src/core/validator.ts +26 -6
- package/src/core.ts +0 -1
- package/src/schema/array.test.ts +2 -2
- package/src/schema/blob.test.ts +317 -49
- package/src/schema/blob.ts +56 -23
- package/src/schema/custom.ts +1 -7
- package/src/schema/params.test.ts +2 -2
- package/src/schema/payload.ts +2 -2
- package/src/schema/ref.ts +1 -5
- package/src/schema/refine.ts +0 -1
- package/src/schema/string.test.ts +63 -0
- package/src/schema/string.ts +1 -1
- package/dist/core/property-key.d.ts +0 -2
- package/dist/core/property-key.d.ts.map +0 -1
- package/dist/core/property-key.js +0 -3
- package/dist/core/property-key.js.map +0 -1
- package/src/core/property-key.ts +0 -1
package/src/schema/blob.ts
CHANGED
|
@@ -1,32 +1,36 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BlobRef,
|
|
3
|
-
BlobRefCheckOptions,
|
|
4
3
|
LegacyBlobRef,
|
|
5
4
|
isBlobRef,
|
|
6
5
|
isLegacyBlobRef,
|
|
6
|
+
parseCidSafe,
|
|
7
7
|
} from '@atproto/lex-data'
|
|
8
8
|
import { Schema, ValidationContext } from '../core.js'
|
|
9
9
|
import { memoizedOptions } from '../util/memoize.js'
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Configuration options for blob schema validation.
|
|
13
|
-
*
|
|
14
|
-
* @property allowLegacy - Whether to allow legacy blob references format
|
|
15
|
-
* @property accept - List of accepted MIME types (supports wildcards like 'image/*' or '*\/*')
|
|
16
|
-
* @property maxSize - Maximum blob size in bytes
|
|
17
13
|
*/
|
|
18
|
-
export type BlobSchemaOptions =
|
|
14
|
+
export type BlobSchemaOptions = {
|
|
19
15
|
/**
|
|
20
16
|
* Whether to allow legacy blob references format
|
|
17
|
+
*
|
|
18
|
+
* @default false
|
|
21
19
|
* @see {@link LegacyBlobRef}
|
|
22
20
|
*/
|
|
23
21
|
allowLegacy?: boolean
|
|
22
|
+
|
|
24
23
|
/**
|
|
25
|
-
* List of accepted
|
|
24
|
+
* List of accepted MIME types (supports wildcards like 'image/*' or '*\/*')
|
|
25
|
+
*
|
|
26
|
+
* @default undefined // accepts all MIME types
|
|
26
27
|
*/
|
|
27
28
|
accept?: string[]
|
|
29
|
+
|
|
28
30
|
/**
|
|
29
|
-
* Maximum size in bytes
|
|
31
|
+
* Maximum blob size in bytes
|
|
32
|
+
*
|
|
33
|
+
* @default undefined // no size limit
|
|
30
34
|
*/
|
|
31
35
|
maxSize?: number
|
|
32
36
|
}
|
|
@@ -61,27 +65,24 @@ export class BlobSchema<
|
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
64
|
-
const blob
|
|
65
|
-
(input as any)?.$type !== undefined
|
|
66
|
-
? isBlobRef(input, this.options)
|
|
67
|
-
? input
|
|
68
|
-
: null
|
|
69
|
-
: this.options?.allowLegacy === true && isLegacyBlobRef(input)
|
|
70
|
-
? input
|
|
71
|
-
: null
|
|
68
|
+
const blob = parseValue.call(ctx, input, this.options)
|
|
72
69
|
|
|
73
70
|
if (!blob) {
|
|
74
71
|
return ctx.issueUnexpectedType(input, 'blob')
|
|
75
72
|
}
|
|
76
73
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
// In non-strict mode, we allow blob refs to pass through without MIME
|
|
75
|
+
// type or size checks.
|
|
76
|
+
if (ctx.options.strict) {
|
|
77
|
+
const accept = this.options?.accept
|
|
78
|
+
if (accept && !matchesMime(blob.mimeType, accept)) {
|
|
79
|
+
return ctx.issueInvalidPropertyValue(blob, 'mimeType', accept)
|
|
80
|
+
}
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
const maxSize = this.options?.maxSize
|
|
83
|
+
if (maxSize != null && 'size' in blob && blob.size > maxSize) {
|
|
84
|
+
return ctx.issueTooBig(blob, 'blob', maxSize, blob.size)
|
|
85
|
+
}
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
return ctx.success(blob)
|
|
@@ -94,6 +95,38 @@ export class BlobSchema<
|
|
|
94
95
|
}
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
function parseValue(
|
|
99
|
+
this: ValidationContext,
|
|
100
|
+
input: unknown,
|
|
101
|
+
options?: BlobSchemaOptions,
|
|
102
|
+
): BlobRef | LegacyBlobRef | null {
|
|
103
|
+
// If there is a $type property, we treat if as a potential BlobRef and
|
|
104
|
+
// validate accordingly.
|
|
105
|
+
if ((input as any)?.$type !== undefined) {
|
|
106
|
+
// Use the context's option for the "strict" check
|
|
107
|
+
return isBlobRef(input, this.options) ? input : null
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// If there is no $type property, we may be dealing with a legacy blob ref. If
|
|
111
|
+
// legacy refs are allowed, validate against the legacy format. If not
|
|
112
|
+
// allowed, but we are in non-strict "parse" mode, coerce legacy refs into
|
|
113
|
+
// standard BlobRef format for backward compatibility. Otherwise, reject the
|
|
114
|
+
// value.
|
|
115
|
+
if (options?.allowLegacy) {
|
|
116
|
+
if (isLegacyBlobRef(input)) {
|
|
117
|
+
return input
|
|
118
|
+
}
|
|
119
|
+
} else if (!this.options.strict && this.options.mode === 'parse') {
|
|
120
|
+
if (isLegacyBlobRef(input)) {
|
|
121
|
+
const { cid, mimeType } = input
|
|
122
|
+
const ref = parseCidSafe(cid)
|
|
123
|
+
if (ref) return { $type: 'blob', ref, mimeType, size: -1 }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return null
|
|
128
|
+
}
|
|
129
|
+
|
|
97
130
|
function matchesMime(mime: string, accepted: string[]): boolean {
|
|
98
131
|
if (accepted.includes('*/*')) return true
|
|
99
132
|
if (accepted.includes(mime)) return true
|
package/src/schema/custom.ts
CHANGED
|
@@ -419,7 +419,7 @@ describe('ParamsSchema', () => {
|
|
|
419
419
|
['name', 'Alice'],
|
|
420
420
|
['bools', 'notabool'],
|
|
421
421
|
]),
|
|
422
|
-
).toThrow('Expected boolean value type
|
|
422
|
+
).toThrow('Expected boolean value type (got string) at $.bools')
|
|
423
423
|
|
|
424
424
|
expect(() =>
|
|
425
425
|
schema.fromURLSearchParams(
|
|
@@ -431,7 +431,7 @@ describe('ParamsSchema', () => {
|
|
|
431
431
|
path: ['foo', 'bar'],
|
|
432
432
|
},
|
|
433
433
|
),
|
|
434
|
-
).toThrow('Expected boolean value type at $.foo.bar.bools
|
|
434
|
+
).toThrow('Expected boolean value type (got string) at $.foo.bar.bools')
|
|
435
435
|
})
|
|
436
436
|
|
|
437
437
|
it('ignores empty string values', () => {
|
package/src/schema/payload.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LexValue } from '@atproto/lex-data'
|
|
2
|
-
import {
|
|
2
|
+
import { InferInput, Schema, Validator } from '../core.js'
|
|
3
3
|
import { ObjectSchema, object } from './object.js'
|
|
4
4
|
|
|
5
5
|
export type { LexValue }
|
|
@@ -15,7 +15,7 @@ type ToBodyType<
|
|
|
15
15
|
TSchema,
|
|
16
16
|
TBinary,
|
|
17
17
|
> = TSchema extends Schema
|
|
18
|
-
?
|
|
18
|
+
? InferInput<TSchema>
|
|
19
19
|
: TEncoding extends `application/json`
|
|
20
20
|
? LexValue
|
|
21
21
|
: TBinary
|
package/src/schema/ref.ts
CHANGED
|
@@ -32,11 +32,7 @@ export type RefSchemaGetter<out TValidator extends Validator> = () => TValidator
|
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
34
|
export class RefSchema<const TValidator extends Validator>
|
|
35
|
-
extends Schema<
|
|
36
|
-
InferInput<TValidator>,
|
|
37
|
-
InferOutput<TValidator>,
|
|
38
|
-
TValidator['__lex']
|
|
39
|
-
>
|
|
35
|
+
extends Schema<InferInput<TValidator>, InferOutput<TValidator>>
|
|
40
36
|
implements WrappedValidator<TValidator>
|
|
41
37
|
{
|
|
42
38
|
readonly type = 'ref' as const
|
package/src/schema/refine.ts
CHANGED
|
@@ -265,6 +265,69 @@ describe('StringSchema', () => {
|
|
|
265
265
|
const result = schema.safeParse('12/25/2023')
|
|
266
266
|
expect(result.success).toBe(false)
|
|
267
267
|
})
|
|
268
|
+
|
|
269
|
+
it('rejects datetime without timezone', () => {
|
|
270
|
+
const result = schema.safeParse('2023-12-25T12:00:00')
|
|
271
|
+
expect(result.success).toBe(false)
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
it('rejects date-only strings', () => {
|
|
275
|
+
// Date-only is not a valid datetime in either strict or loose mode
|
|
276
|
+
const result = schema.safeParse('2023-12-25')
|
|
277
|
+
expect(result.success).toBe(false)
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
describe('loose validation', () => {
|
|
281
|
+
it('accepts datetime without timezone', () => {
|
|
282
|
+
const result = schema.safeParse('2023-12-25T12:00:00', {
|
|
283
|
+
strict: false,
|
|
284
|
+
})
|
|
285
|
+
expect(result.success).toBe(true)
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
it('accepts datetime without separator', () => {
|
|
289
|
+
const result = schema.safeParse('20231225T120000', { strict: false })
|
|
290
|
+
expect(result.success).toBe(true)
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
it('rejects datetime with "-00:00" timezone offset', () => {
|
|
294
|
+
const result = schema.safeParse('2023-12-25T12:00:00-00:00', {
|
|
295
|
+
strict: false,
|
|
296
|
+
})
|
|
297
|
+
expect(result.success).toBe(false)
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
it('rejects date-only strings', () => {
|
|
301
|
+
const result = schema.safeParse('2023-12-25', { strict: false })
|
|
302
|
+
expect(result.success).toBe(false)
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
it('accepts datetime with timezone offset', () => {
|
|
306
|
+
const result = schema.safeParse('2023-12-25T12:00:00+05:30', {
|
|
307
|
+
strict: false,
|
|
308
|
+
})
|
|
309
|
+
expect(result.success).toBe(true)
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
it('still rejects completely invalid strings', () => {
|
|
313
|
+
expect(schema.safeParse('not a date', { strict: false }).success).toBe(
|
|
314
|
+
false,
|
|
315
|
+
)
|
|
316
|
+
expect(schema.safeParse('12/25/2023', { strict: false }).success).toBe(
|
|
317
|
+
false,
|
|
318
|
+
)
|
|
319
|
+
expect(schema.safeParse('', { strict: false }).success).toBe(false)
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
it('uses strict mode by default', () => {
|
|
323
|
+
// Datetime without timezone is not AT Protocol compliant
|
|
324
|
+
expect(schema.safeParse('2023-12-25T12:00:00').success).toBe(false)
|
|
325
|
+
// Date-only is not AT Protocol compliant
|
|
326
|
+
expect(schema.safeParse('2023-12-25').success).toBe(false)
|
|
327
|
+
// With timezone offset (AT Protocol compliant)
|
|
328
|
+
expect(schema.safeParse('2023-12-25T12:00:00+05:30').success).toBe(true)
|
|
329
|
+
})
|
|
330
|
+
})
|
|
268
331
|
})
|
|
269
332
|
|
|
270
333
|
describe('format: uri', () => {
|
package/src/schema/string.ts
CHANGED
|
@@ -119,7 +119,7 @@ export class StringSchema<
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
const format = this.options.format
|
|
122
|
-
if (format != null && !isStringFormat(str, format)) {
|
|
122
|
+
if (format != null && !isStringFormat(str, format, ctx.options)) {
|
|
123
123
|
return ctx.issueInvalidFormat(str, format)
|
|
124
124
|
}
|
|
125
125
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"property-key.d.ts","sourceRoot":"","sources":["../../src/core/property-key.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"property-key.js","sourceRoot":"","sources":["../../src/core/property-key.ts"],"names":[],"mappings":"","sourcesContent":["export type PropertyKey = string | number\n"]}
|
package/src/core/property-key.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type PropertyKey = string | number
|