@atproto/lex-schema 0.0.16 → 0.0.18
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 -11
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +10 -16
- package/dist/core/schema.js.map +1 -1
- package/dist/core/validation-error.d.ts +2 -2
- 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/validation-issue.d.ts.map +1 -1
- package/dist/core/validation-issue.js +19 -38
- package/dist/core/validation-issue.js.map +1 -1
- package/dist/helpers.d.ts +4 -3
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +6 -1
- package/dist/helpers.js.map +1 -1
- package/dist/schema/blob.d.ts +6 -20
- package/dist/schema/blob.d.ts.map +1 -1
- package/dist/schema/blob.js +23 -28
- package/dist/schema/blob.js.map +1 -1
- package/dist/schema/payload.d.ts.map +1 -1
- package/dist/schema/payload.js +2 -3
- package/dist/schema/payload.js.map +1 -1
- package/dist/schema/record.d.ts +6 -8
- 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/regexp.d.ts +3 -2
- package/dist/schema/regexp.d.ts.map +1 -1
- package/dist/schema/regexp.js +6 -4
- package/dist/schema/regexp.js.map +1 -1
- package/dist/schema/string.d.ts.map +1 -1
- package/dist/schema/string.js +9 -2
- package/dist/schema/string.js.map +1 -1
- package/dist/schema/typed-object.d.ts +5 -7
- 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/package.json +3 -3
- package/src/core/$type.test.ts +9 -5
- package/src/core/schema.ts +19 -16
- package/src/core/validation-error.ts +2 -2
- package/src/core/validation-issue.ts +20 -36
- package/src/helpers.ts +7 -1
- package/src/schema/array.test.ts +1 -1
- package/src/schema/blob.test.ts +223 -263
- package/src/schema/blob.ts +27 -46
- package/src/schema/params.test.ts +2 -2
- package/src/schema/payload.ts +2 -3
- package/src/schema/record.test.ts +135 -17
- package/src/schema/record.ts +14 -9
- package/src/schema/regexp.ts +14 -4
- package/src/schema/string.ts +8 -2
- package/src/schema/typed-object.test.ts +77 -0
- package/src/schema/typed-object.ts +11 -10
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typed-object.d.ts","sourceRoot":"","sources":["../../src/schema/typed-object.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"typed-object.d.ts","sourceRoot":"","sources":["../../src/schema/typed-object.ts"],"names":[],"mappings":"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"}
|
|
@@ -44,7 +44,7 @@ class TypedObjectSchema extends core_js_1.Schema {
|
|
|
44
44
|
return ctx.validate(input, this.schema);
|
|
45
45
|
}
|
|
46
46
|
build(input) {
|
|
47
|
-
return
|
|
47
|
+
return (0, core_js_1.$typed)(input, this.$type);
|
|
48
48
|
}
|
|
49
49
|
isTypeOf(value) {
|
|
50
50
|
return value.$type === undefined || value.$type === this.$type;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typed-object.js","sourceRoot":"","sources":["../../src/schema/typed-object.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"typed-object.js","sourceRoot":"","sources":["../../src/schema/typed-object.ts"],"names":[],"mappings":";;;AA8KA,kCAMC;AApLD,gDAAyD;AACzD,wCAcmB;AACnB,+DAAuD;AASvD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAa,iBAGX,SAAQ,gBAGT;IAIY;IACA;IAJF,IAAI,GAAG,aAAsB,CAAA;IAEtC,YACW,KAAY,EACZ,MAAc;QAEvB,KAAK,EAAE,CAAA;QAHE,UAAK,GAAL,KAAK,CAAO;QACZ,WAAM,GAAN,MAAM,CAAQ;IAGzB,CAAC;IAED,iBAAiB,CAAC,KAAc,EAAE,GAAsB;QACtD,IAAI,CAAC,IAAA,wBAAa,EAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,GAAG,CAAC,mBAAmB,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;QACjD,CAAC;QAED,IACE,OAAO,IAAI,KAAK;YAChB,KAAK,CAAC,KAAK,KAAK,SAAS;YACzB,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,EAC1B,CAAC;YACD,OAAO,GAAG,CAAC,yBAAyB,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;QACpE,CAAC;QAED,OAAO,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;IACzC,CAAC;IAQD,KAAK,CAAC,KAA8B;QAClC,OAAO,IAAA,gBAAM,EAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;IAClC,CAAC;IAED,QAAQ,CACN,KAAa;QAEb,OAAO,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,CAAA;IAChE,CAAC;IAED;;;OAGG;IACH,IAAI,MAAM;QACR,OAAO,IAAA,+BAAY,EAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;IAC5D,CAAC;IAED;;;OAGG;IACH,IAAI,SAAS;QACX,OAAO,IAAA,+BAAY,EAAC,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;IAClE,CAAC;CACF;AA/DD,8CA+DC;AAmED,wBAAwB;AACxB,SAAgB,WAAW,CAIzB,IAAO,EAAE,IAAO,EAAE,SAAY;IAC9B,OAAO,IAAI,iBAAiB,CAAiB,IAAA,eAAK,EAAC,IAAI,EAAE,IAAI,CAAC,EAAE,SAAS,CAAC,CAAA;AAC5E,CAAC","sourcesContent":["import { LexMap, isPlainObject } from '@atproto/lex-data'\nimport {\n $Type,\n $TypeOf,\n $Typed,\n $TypedMaybe,\n $type,\n $typed,\n InferInput,\n InferOutput,\n NsidString,\n Schema,\n Unknown$TypedObject,\n ValidationContext,\n Validator,\n} from '../core.js'\nimport { lazyProperty } from '../util/lazy-property.js'\n\nexport type MaybeTypedObject<\n TType extends $Type,\n TValue extends { $type?: unknown } = { $type?: unknown },\n> = TValue extends { $type?: TType }\n ? TValue\n : $TypedMaybe<Exclude<TValue, Unknown$TypedObject>, TType>\n\n/**\n * Schema for typed objects in Lexicon unions.\n *\n * Typed objects have a `$type` field that identifies which variant they are\n * in a union. The `$type` can be omitted in input (it's implicit), but if\n * present, it must match the expected value.\n *\n * @template TType - The $type string literal type\n * @template TShape - The validator type for the object's shape\n *\n * @example\n * ```ts\n * const schema = new TypedObjectSchema(\n * 'app.bsky.embed.images#view',\n * l.object({ images: l.array(imageSchema) })\n * )\n * ```\n */\nexport class TypedObjectSchema<\n const TType extends $Type = $Type,\n const TShape extends Validator<LexMap> = Validator<LexMap>,\n> extends Schema<\n $TypedMaybe<InferInput<TShape>, TType>,\n $TypedMaybe<InferOutput<TShape>, TType>\n> {\n readonly type = 'typedObject' as const\n\n constructor(\n readonly $type: TType,\n readonly schema: TShape,\n ) {\n super()\n }\n\n validateInContext(input: unknown, ctx: ValidationContext) {\n if (!isPlainObject(input)) {\n return ctx.issueUnexpectedType(input, 'object')\n }\n\n if (\n '$type' in input &&\n input.$type !== undefined &&\n input.$type !== this.$type\n ) {\n return ctx.issueInvalidPropertyValue(input, '$type', [this.$type])\n }\n\n return ctx.validate(input, this.schema)\n }\n\n build(\n input: Omit<InferOutput<TShape>, '$type'>,\n ): $Typed<InferOutput<TShape>, TType>\n build(\n input: Omit<InferInput<TShape>, '$type'>,\n ): $Typed<InferInput<TShape>, TType>\n build(input: Record<string, unknown>) {\n return $typed(input, this.$type)\n }\n\n isTypeOf<TValue extends Record<string, unknown>>(\n value: TValue,\n ): value is MaybeTypedObject<TType, TValue> {\n return value.$type === undefined || value.$type === this.$type\n }\n\n /**\n * Bound alias for {@link build} for compatibility with generated utilities.\n * @see {@link build}\n */\n get $build(): typeof this.build {\n return lazyProperty(this, '$build', this.build.bind(this))\n }\n\n /**\n * Bound alias for {@link isTypeOf} for compatibility with generated utilities.\n * @see {@link isTypeOf}\n */\n get $isTypeOf(): typeof this.isTypeOf {\n return lazyProperty(this, '$isTypeOf', this.isTypeOf.bind(this))\n }\n}\n\n/**\n * Creates a typed object schema for use in Lexicon unions.\n *\n * Typed objects are identified by their `$type` field, which combines an NSID\n * and a hash (e.g., 'app.bsky.embed.images#view'). Used for union variants.\n *\n * This function offers two overloads:\n * - One that infers the type from arguments (no circular reference support)\n * - One with explicit interface for codegen with circular references\n *\n * @param nsid - The NSID part of the type (e.g., 'app.bsky.embed.images')\n * @param hash - The hash part of the type (e.g., 'view'), defaults to 'main'\n * @param validator - Schema for validating the object properties\n * @returns A new {@link TypedObjectSchema} instance\n *\n * @example\n * ```ts\n * // Image embed view\n * const imageViewSchema = l.typedObject(\n * 'app.bsky.embed.images',\n * 'view',\n * l.object({\n * images: l.array(l.object({\n * thumb: l.string(),\n * fullsize: l.string(),\n * alt: l.string(),\n * })),\n * })\n * )\n *\n * // Main type (hash defaults to 'main')\n * const postViewSchema = l.typedObject(\n * 'app.bsky.feed.defs',\n * 'postView',\n * l.object({ uri: l.string(), cid: l.string(), author: authorSchema })\n * )\n *\n * // Use $isTypeOf to narrow union types\n * if (imageViewSchema.$isTypeOf(embed)) {\n * // embed is narrowed to image view type\n * }\n *\n * // Use $build to construct typed objects\n * const view = imageViewSchema.$build({ images: [...] })\n * // view.$type === 'app.bsky.embed.images#view'\n * ```\n */\nexport function typedObject<\n const N extends NsidString,\n const H extends string,\n const S extends Validator<LexMap>,\n>(nsid: N, hash: H, validator: S): TypedObjectSchema<$Type<N, H>, S>\nexport function typedObject<V extends { $type?: $Type }>(\n nsid: V extends { $type?: infer T extends string }\n ? T extends `${infer N}#${string}`\n ? N\n : T // (T is a \"main\" type, so already an NSID)\n : never,\n hash: V extends { $type?: infer T extends string }\n ? T extends `${string}#${infer H}`\n ? H\n : 'main'\n : never,\n validator: Validator<Omit<V, '$type'>>,\n): TypedObjectSchema<$TypeOf<V>, Validator<V>>\n/*@__NO_SIDE_EFFECTS__*/\nexport function typedObject<\n const N extends NsidString,\n const H extends string,\n const S extends Validator<LexMap>,\n>(nsid: N, hash: H, validator: S) {\n return new TypedObjectSchema<$Type<N, H>, S>($type(nsid, hash), validator)\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/lex-schema",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Lexicon schema system for AT Lexicons",
|
|
6
6
|
"keywords": [
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"@standard-schema/spec": "^1.1.0",
|
|
39
39
|
"iso-datestring-validator": "^2.2.2",
|
|
40
40
|
"tslib": "^2.8.1",
|
|
41
|
-
"@atproto/syntax": "^0.5.
|
|
42
|
-
"@atproto/lex-data": "^0.0.
|
|
41
|
+
"@atproto/syntax": "^0.5.3",
|
|
42
|
+
"@atproto/lex-data": "^0.0.15"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"vitest": "^4.0.16"
|
package/src/core/$type.test.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { describe, it } from 'vitest'
|
|
1
|
+
import { describe, expectTypeOf, it } from 'vitest'
|
|
2
2
|
import { LexMap } from '@atproto/lex-data'
|
|
3
|
-
import { Unknown$TypedObject } from './$type.js'
|
|
3
|
+
import { Unknown$Type, Unknown$TypedObject } from './$type.js'
|
|
4
4
|
|
|
5
5
|
describe('Unknown$TypedObject', () => {
|
|
6
6
|
it('allows assigning Unknown$TypedObject to LexMap', () => {
|
|
7
7
|
function expectLexMap(_value: LexMap) {}
|
|
8
8
|
|
|
9
|
-
const someObject = {
|
|
10
|
-
$type: 'some-type',
|
|
11
|
-
|
|
9
|
+
const someObject: Unknown$TypedObject = {
|
|
10
|
+
$type: 'some-type' as Unknown$Type,
|
|
11
|
+
// @ts-expect-error should not allow arbitrary properties
|
|
12
|
+
foo: 'bar',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
expectTypeOf(someObject).toEqualTypeOf<Unknown$TypedObject>()
|
|
12
16
|
|
|
13
17
|
expectLexMap(someObject)
|
|
14
18
|
|
package/src/core/schema.ts
CHANGED
|
@@ -120,8 +120,11 @@ export abstract class Schema<out TInput = unknown, out TOutput = TInput>
|
|
|
120
120
|
* will typically arise in generic contexts, where the narrowed type is not
|
|
121
121
|
* needed.
|
|
122
122
|
*/
|
|
123
|
-
assert(
|
|
124
|
-
|
|
123
|
+
assert(
|
|
124
|
+
input: unknown,
|
|
125
|
+
options?: ValidateOptions,
|
|
126
|
+
): asserts input is InferInput<this> {
|
|
127
|
+
const result = this.safeValidate(input, options)
|
|
125
128
|
if (!result.success) throw result.reason
|
|
126
129
|
}
|
|
127
130
|
|
|
@@ -131,8 +134,8 @@ export abstract class Schema<out TInput = unknown, out TOutput = TInput>
|
|
|
131
134
|
* every name in the call target to be declared with an explicit type
|
|
132
135
|
* annotation. ts(2775)_" errors.
|
|
133
136
|
*/
|
|
134
|
-
check(input: unknown): void {
|
|
135
|
-
this.assert(input)
|
|
137
|
+
check(input: unknown, options?: ValidateOptions): void {
|
|
138
|
+
this.assert(input, options)
|
|
136
139
|
}
|
|
137
140
|
|
|
138
141
|
/**
|
|
@@ -140,8 +143,8 @@ export abstract class Schema<out TInput = unknown, out TOutput = TInput>
|
|
|
140
143
|
* schema, otherwise throws. This is the same as calling {@link parse}() with
|
|
141
144
|
* `mode: "validate"`.
|
|
142
145
|
*/
|
|
143
|
-
cast<I>(input: I): I & InferInput<this> {
|
|
144
|
-
const result =
|
|
146
|
+
cast<I>(input: I, options?: ValidateOptions): I & InferInput<this> {
|
|
147
|
+
const result = this.safeValidate(input, options)
|
|
145
148
|
if (result.success) return result.value
|
|
146
149
|
throw result.reason
|
|
147
150
|
}
|
|
@@ -149,9 +152,6 @@ export abstract class Schema<out TInput = unknown, out TOutput = TInput>
|
|
|
149
152
|
/**
|
|
150
153
|
* Type guard that checks if the input matches this schema.
|
|
151
154
|
*
|
|
152
|
-
* @param input - The value to check
|
|
153
|
-
* @returns `true` if the input is valid according to this schema
|
|
154
|
-
*
|
|
155
155
|
* @example
|
|
156
156
|
* ```typescript
|
|
157
157
|
* if (schema.matches(data)) {
|
|
@@ -160,8 +160,11 @@ export abstract class Schema<out TInput = unknown, out TOutput = TInput>
|
|
|
160
160
|
* }
|
|
161
161
|
* ```
|
|
162
162
|
*/
|
|
163
|
-
matches<I>(
|
|
164
|
-
|
|
163
|
+
matches<I>(
|
|
164
|
+
input: I,
|
|
165
|
+
options?: ValidateOptions,
|
|
166
|
+
): input is I & InferInput<this> {
|
|
167
|
+
const result = this.safeValidate(input, options)
|
|
165
168
|
return result.success
|
|
166
169
|
}
|
|
167
170
|
|
|
@@ -171,9 +174,6 @@ export abstract class Schema<out TInput = unknown, out TOutput = TInput>
|
|
|
171
174
|
* This is useful for optional filtering operations where you want to
|
|
172
175
|
* conditionally extract values that match a schema.
|
|
173
176
|
*
|
|
174
|
-
* @param input - The value to check
|
|
175
|
-
* @returns The input value with narrowed type if valid, otherwise `undefined`
|
|
176
|
-
*
|
|
177
177
|
* @example
|
|
178
178
|
* ```typescript
|
|
179
179
|
* const validData = schema.ifMatches(data)
|
|
@@ -183,8 +183,11 @@ export abstract class Schema<out TInput = unknown, out TOutput = TInput>
|
|
|
183
183
|
* }
|
|
184
184
|
* ```
|
|
185
185
|
*/
|
|
186
|
-
ifMatches<I>(
|
|
187
|
-
|
|
186
|
+
ifMatches<I>(
|
|
187
|
+
input: I,
|
|
188
|
+
options?: ValidateOptions,
|
|
189
|
+
): (I & InferInput<this>) | undefined {
|
|
190
|
+
return this.matches(input, options) ? input : undefined
|
|
188
191
|
}
|
|
189
192
|
|
|
190
193
|
/**
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
* new IssueInvalidType(['user', 'age'], 'hello', ['number'])
|
|
24
24
|
* ])
|
|
25
25
|
* console.log(error.message)
|
|
26
|
-
* // "Expected
|
|
26
|
+
* // "Expected integer value type (got "some-string") at $.user.age"
|
|
27
27
|
*
|
|
28
28
|
* console.log(error.issues.length) // 1
|
|
29
29
|
* console.log(error.toJSON())
|
|
@@ -46,7 +46,7 @@ export class LexValidationError
|
|
|
46
46
|
* Issues are aggregated when possible (e.g., multiple invalid type issues
|
|
47
47
|
* at the same path are combined into a single issue listing all expected types).
|
|
48
48
|
*/
|
|
49
|
-
readonly issues: Issue[]
|
|
49
|
+
readonly issues: readonly Issue[]
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* Creates a new validation error from a list of issues.
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { ifCid, isLegacyBlobRef, isPlainObject } from '@atproto/lex-data'
|
|
2
2
|
|
|
3
|
+
const STRING_PREVIEW_MAX_LENGTH = 48
|
|
4
|
+
const STRING_PREVIEW_TRUNCATED_SUFFIX = '…'
|
|
5
|
+
|
|
3
6
|
/**
|
|
4
7
|
* Abstract base class for all validation issues.
|
|
5
8
|
*
|
|
@@ -120,7 +123,7 @@ export class IssueInvalidType extends Issue {
|
|
|
120
123
|
}
|
|
121
124
|
|
|
122
125
|
override get message(): string {
|
|
123
|
-
return `Expected ${oneOf(this.expected.map(stringifyExpectedType))} value type (got ${
|
|
126
|
+
return `Expected ${oneOf(this.expected.map(stringifyExpectedType))} value type (got ${stringifyValue(this.input)})`
|
|
124
127
|
}
|
|
125
128
|
|
|
126
129
|
toJSON() {
|
|
@@ -289,55 +292,36 @@ function oneOf(arr: readonly string[]): string {
|
|
|
289
292
|
return `one of ${arr.slice(0, -1).join(', ')} or ${arr.at(-1)}`
|
|
290
293
|
}
|
|
291
294
|
|
|
292
|
-
function stringifyType(value: unknown): string {
|
|
293
|
-
switch (typeof value) {
|
|
294
|
-
case 'object':
|
|
295
|
-
if (value === null) return 'null'
|
|
296
|
-
if (Array.isArray(value)) return 'array'
|
|
297
|
-
if (ifCid(value)) return 'cid'
|
|
298
|
-
if (isLegacyBlobRef(value)) return 'legacy-blob'
|
|
299
|
-
if (value instanceof Date) return 'date'
|
|
300
|
-
if (value instanceof RegExp) return 'regexp'
|
|
301
|
-
if (value instanceof Map) return 'map'
|
|
302
|
-
if (value instanceof Set) return 'set'
|
|
303
|
-
return 'object'
|
|
304
|
-
case 'number':
|
|
305
|
-
if (Number.isInteger(value) && Number.isSafeInteger(value)) {
|
|
306
|
-
return 'integer'
|
|
307
|
-
}
|
|
308
|
-
if (Number.isNaN(value)) {
|
|
309
|
-
return 'NaN'
|
|
310
|
-
}
|
|
311
|
-
if (value === Infinity) {
|
|
312
|
-
return 'Infinity'
|
|
313
|
-
}
|
|
314
|
-
if (value === -Infinity) {
|
|
315
|
-
return '-Infinity'
|
|
316
|
-
}
|
|
317
|
-
return 'float'
|
|
318
|
-
default:
|
|
319
|
-
return typeof value
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
295
|
function stringifyValue(value: unknown): string {
|
|
324
296
|
switch (typeof value) {
|
|
325
297
|
case 'bigint':
|
|
326
298
|
return `${value}n`
|
|
327
299
|
case 'number':
|
|
328
|
-
case 'string':
|
|
329
300
|
case 'boolean':
|
|
330
|
-
return
|
|
301
|
+
return String(value)
|
|
302
|
+
case 'string':
|
|
303
|
+
return JSON.stringify(
|
|
304
|
+
value.length < STRING_PREVIEW_MAX_LENGTH
|
|
305
|
+
? value
|
|
306
|
+
: `${value.slice(0, STRING_PREVIEW_MAX_LENGTH - STRING_PREVIEW_TRUNCATED_SUFFIX.length)}${STRING_PREVIEW_TRUNCATED_SUFFIX}`,
|
|
307
|
+
)
|
|
331
308
|
case 'object':
|
|
309
|
+
if (value === null) return 'null'
|
|
332
310
|
if (Array.isArray(value)) {
|
|
333
311
|
return `[${stringifyArray(value, stringifyValue)}]`
|
|
334
312
|
}
|
|
335
313
|
if (isPlainObject(value)) {
|
|
336
314
|
return `{${stringifyArray(Object.entries(value), stringifyObjectEntry)}}`
|
|
337
315
|
}
|
|
338
|
-
|
|
316
|
+
if (ifCid(value)) return 'cid'
|
|
317
|
+
if (isLegacyBlobRef(value)) return 'legacy-blob'
|
|
318
|
+
if (value instanceof Date) return 'date'
|
|
319
|
+
if (value instanceof RegExp) return 'regexp'
|
|
320
|
+
if (value instanceof Map) return 'map'
|
|
321
|
+
if (value instanceof Set) return 'set'
|
|
322
|
+
return 'object'
|
|
339
323
|
default:
|
|
340
|
-
return
|
|
324
|
+
return typeof value
|
|
341
325
|
}
|
|
342
326
|
}
|
|
343
327
|
|
package/src/helpers.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
Subscription,
|
|
10
10
|
object,
|
|
11
11
|
optional,
|
|
12
|
+
regexp,
|
|
12
13
|
string,
|
|
13
14
|
} from './schema.js'
|
|
14
15
|
|
|
@@ -103,7 +104,12 @@ export type InferMethodError<
|
|
|
103
104
|
M extends Procedure | Query | Subscription = Procedure | Query | Subscription,
|
|
104
105
|
> = M extends { errors: readonly (infer E extends string)[] } ? E : never
|
|
105
106
|
|
|
107
|
+
/**
|
|
108
|
+
* @see {@link https://atproto.com/specs/xrpc#error-responses}
|
|
109
|
+
*/
|
|
106
110
|
export const lexErrorDataSchema = object({
|
|
107
|
-
error
|
|
111
|
+
// type name of the error (generic ASCII constant, no whitespace)
|
|
112
|
+
error: regexp(/^[\w_-]+$/, 'Expected ASCII constant with no whitespace'),
|
|
113
|
+
// description of the error, appropriate for display to humans
|
|
108
114
|
message: optional(string()),
|
|
109
115
|
}) satisfies Schema<LexErrorData>
|
package/src/schema/array.test.ts
CHANGED