Suite of AT Protocol TypeScript libraries built on web standards
1import { z } from "zod";
2import { validateLanguage } from "@atp/common";
3import { isValidNsid } from "@atp/syntax";
4import { requiredPropertiesRefinement } from "./util.ts";
5
6export const languageSchema: z.ZodString = z
7 .string()
8 .refine(validateLanguage, "Invalid BCP47 language tag");
9
10export const lexLang: LexLangType = z.record(
11 languageSchema,
12 z.string().optional(),
13);
14
15type LexLangType = z.ZodRecord<z.ZodString, z.ZodOptional<z.ZodString>>;
16export type LexLang = z.infer<LexLangType>;
17
18// primitives
19// =
20
21export const lexBoolean: LexBooleanType = z.object({
22 type: z.literal("boolean"),
23 description: z.string().optional(),
24 default: z.boolean().optional(),
25 const: z.boolean().optional(),
26});
27type LexBooleanType = z.ZodObject<{
28 type: z.ZodLiteral<"boolean">;
29 description: z.ZodOptional<z.ZodString>;
30 default: z.ZodOptional<z.ZodBoolean>;
31 const: z.ZodOptional<z.ZodBoolean>;
32}, z.core.$strip>;
33export type LexBoolean = z.infer<LexBooleanType>;
34
35export const lexInteger: LexIntegerType = z.object({
36 type: z.literal("integer"),
37 description: z.string().optional(),
38 default: z.number().int().optional(),
39 minimum: z.number().int().optional(),
40 maximum: z.number().int().optional(),
41 enum: z.number().int().array().optional(),
42 const: z.number().int().optional(),
43});
44type LexIntegerType = z.ZodObject<{
45 type: z.ZodLiteral<"integer">;
46 description: z.ZodOptional<z.ZodString>;
47 default: z.ZodOptional<z.ZodNumber>;
48 minimum: z.ZodOptional<z.ZodNumber>;
49 maximum: z.ZodOptional<z.ZodNumber>;
50 enum: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
51 const: z.ZodOptional<z.ZodNumber>;
52}, z.core.$strip>;
53export type LexInteger = z.infer<LexIntegerType>;
54
55export const lexStringFormat: LexStringFormatType = z.enum([
56 "datetime",
57 "uri",
58 "at-uri",
59 "did",
60 "handle",
61 "at-identifier",
62 "nsid",
63 "cid",
64 "language",
65 "tid",
66 "record-key",
67]);
68type LexStringFormatType = z.ZodEnum<{
69 datetime: "datetime";
70 uri: "uri";
71 "at-uri": "at-uri";
72 did: "did";
73 handle: "handle";
74 "at-identifier": "at-identifier";
75 nsid: "nsid";
76 cid: "cid";
77 language: "language";
78 tid: "tid";
79 "record-key": "record-key";
80}>;
81export type LexStringFormat = z.infer<LexStringFormatType>;
82
83export const lexString: LexStringType = z.object({
84 type: z.literal("string"),
85 format: lexStringFormat.optional(),
86 description: z.string().optional(),
87 default: z.string().optional(),
88 minLength: z.number().int().optional(),
89 maxLength: z.number().int().optional(),
90 minGraphemes: z.number().int().optional(),
91 maxGraphemes: z.number().int().optional(),
92 enum: z.string().array().optional(),
93 const: z.string().optional(),
94 knownValues: z.string().array().optional(),
95});
96type LexStringType = z.ZodObject<{
97 type: z.ZodLiteral<"string">;
98 format: z.ZodOptional<
99 z.ZodEnum<{
100 datetime: "datetime";
101 uri: "uri";
102 "at-uri": "at-uri";
103 did: "did";
104 handle: "handle";
105 "at-identifier": "at-identifier";
106 nsid: "nsid";
107 cid: "cid";
108 language: "language";
109 tid: "tid";
110 "record-key": "record-key";
111 }>
112 >;
113 description: z.ZodOptional<z.ZodString>;
114 default: z.ZodOptional<z.ZodString>;
115 minLength: z.ZodOptional<z.ZodNumber>;
116 maxLength: z.ZodOptional<z.ZodNumber>;
117 minGraphemes: z.ZodOptional<z.ZodNumber>;
118 maxGraphemes: z.ZodOptional<z.ZodNumber>;
119 enum: z.ZodOptional<z.ZodArray<z.ZodString>>;
120 const: z.ZodOptional<z.ZodString>;
121 knownValues: z.ZodOptional<z.ZodArray<z.ZodString>>;
122}, z.core.$strip>;
123export type LexString = z.infer<LexStringType>;
124
125export const lexUnknown: LexUnknownType = z.object({
126 type: z.literal("unknown"),
127 description: z.string().optional(),
128});
129type LexUnknownType = z.ZodObject<{
130 type: z.ZodLiteral<"unknown">;
131 description: z.ZodOptional<z.ZodString>;
132}, z.core.$strip>;
133export type LexUnknown = z.infer<LexUnknownType>;
134
135export const lexPrimitive: LexPrimitiveType = z.discriminatedUnion("type", [
136 lexBoolean,
137 lexInteger,
138 lexString,
139 lexUnknown,
140]);
141type LexPrimitiveType = z.ZodDiscriminatedUnion<
142 [LexBooleanType, LexIntegerType, LexStringType, LexUnknownType],
143 "type"
144>;
145export type LexPrimitive = z.infer<LexPrimitiveType>;
146
147// ipld types
148// =
149
150export const lexBytes: LexBytesType = z.object({
151 type: z.literal("bytes"),
152 description: z.string().optional(),
153 maxLength: z.number().optional(),
154 minLength: z.number().optional(),
155});
156type LexBytesType = z.ZodObject<{
157 type: z.ZodLiteral<"bytes">;
158 description: z.ZodOptional<z.ZodString>;
159 maxLength: z.ZodOptional<z.ZodNumber>;
160 minLength: z.ZodOptional<z.ZodNumber>;
161}, z.core.$strip>;
162export type LexBytes = z.infer<LexBytesType>;
163
164export const lexCidLink: LexCidLinkType = z.object({
165 type: z.literal("cid-link"),
166 description: z.string().optional(),
167});
168type LexCidLinkType = z.ZodObject<{
169 type: z.ZodLiteral<"cid-link">;
170 description: z.ZodOptional<z.ZodString>;
171}, z.core.$strip>;
172export type LexCidLink = z.infer<LexCidLinkType>;
173
174export const lexIpldType: LexIpldTypeType = z.discriminatedUnion("type", [
175 lexBytes,
176 lexCidLink,
177]);
178type LexIpldTypeType = z.ZodDiscriminatedUnion<
179 [LexBytesType, LexCidLinkType],
180 "type"
181>;
182export type LexIpldType = z.infer<LexIpldTypeType>;
183
184// references
185// =
186
187export const lexRef: LexRefType = z.object({
188 type: z.literal("ref"),
189 description: z.string().optional(),
190 ref: z.string(),
191});
192type LexRefType = z.ZodObject<{
193 type: z.ZodLiteral<"ref">;
194 description: z.ZodOptional<z.ZodString>;
195 ref: z.ZodString;
196}, z.core.$strip>;
197export type LexRef = z.infer<LexRefType>;
198
199export const lexRefUnion: LexRefUnionType = z.object({
200 type: z.literal("union"),
201 description: z.string().optional(),
202 refs: z.string().array(),
203 closed: z.boolean().optional(),
204});
205type LexRefUnionType = z.ZodObject<{
206 type: z.ZodLiteral<"union">;
207 description: z.ZodOptional<z.ZodString>;
208 refs: z.ZodArray<z.ZodString>;
209 closed: z.ZodOptional<z.ZodBoolean>;
210}, z.core.$strip>;
211export type LexRefUnion = z.infer<LexRefUnionType>;
212
213export const lexRefVariant: LexRefVariantType = z.discriminatedUnion("type", [
214 lexRef,
215 lexRefUnion,
216]);
217type LexRefVariantType = z.ZodDiscriminatedUnion<
218 [LexRefType, LexRefUnionType],
219 "type"
220>;
221export type LexRefVariant = z.infer<LexRefVariantType>;
222
223// blobs
224// =
225
226export const lexBlob: LexBlobType = z.object({
227 type: z.literal("blob"),
228 description: z.string().optional(),
229 accept: z.string().array().optional(),
230 maxSize: z.number().optional(),
231});
232type LexBlobType = z.ZodObject<{
233 type: z.ZodLiteral<"blob">;
234 description: z.ZodOptional<z.ZodString>;
235 accept: z.ZodOptional<z.ZodArray<z.ZodString>>;
236 maxSize: z.ZodOptional<z.ZodNumber>;
237}, z.core.$strip>;
238export type LexBlob = z.infer<LexBlobType>;
239
240// complex types
241// =
242
243export const lexArray: LexArrayType = z.object({
244 type: z.literal("array"),
245 description: z.string().optional(),
246 items: z.discriminatedUnion("type", [
247 // lexPrimitive
248 lexBoolean,
249 lexInteger,
250 lexString,
251 lexUnknown,
252 // lexIpldType
253 lexBytes,
254 lexCidLink,
255 // lexRefVariant
256 lexRef,
257 lexRefUnion,
258 // other
259 lexBlob,
260 ]),
261 minLength: z.number().int().optional(),
262 maxLength: z.number().int().optional(),
263});
264type LexArrayType = z.ZodObject<{
265 type: z.ZodLiteral<"array">;
266 description: z.ZodOptional<z.ZodString>;
267 items: z.ZodDiscriminatedUnion<
268 [
269 LexBooleanType,
270 LexIntegerType,
271 LexStringType,
272 LexUnknownType,
273 LexBytesType,
274 LexCidLinkType,
275 LexRefType,
276 LexRefUnionType,
277 LexBlobType,
278 ],
279 "type"
280 >;
281 minLength: z.ZodOptional<z.ZodNumber>;
282 maxLength: z.ZodOptional<z.ZodNumber>;
283}, z.core.$strip>;
284export type LexArray = z.infer<LexArrayType>;
285
286export const lexPrimitiveArray: LexPrimitiveArrayType = z.object({
287 ...lexArray.shape,
288 items: lexPrimitive,
289});
290type LexPrimitiveArrayType = z.ZodObject<{
291 items: LexPrimitiveType;
292 type: z.ZodLiteral<"array">;
293 description: z.ZodOptional<z.ZodString>;
294 minLength: z.ZodOptional<z.ZodNumber>;
295 maxLength: z.ZodOptional<z.ZodNumber>;
296}, z.core.$strip>;
297export type LexPrimitiveArray = z.infer<LexPrimitiveArrayType>;
298
299export const lexToken: LexTokenType = z.object({
300 type: z.literal("token"),
301 description: z.string().optional(),
302});
303type LexTokenType = z.ZodObject<{
304 type: z.ZodLiteral<"token">;
305 description: z.ZodOptional<z.ZodString>;
306}, z.core.$strip>;
307export type LexToken = z.infer<LexTokenType>;
308
309export const lexObject: LexObjectType = z
310 .object({
311 type: z.literal("object"),
312 description: z.string().optional(),
313 required: z.string().array().optional(),
314 nullable: z.string().array().optional(),
315 properties: z.record(
316 z.string(),
317 z.discriminatedUnion("type", [
318 lexArray,
319
320 // lexPrimitive
321 lexBoolean,
322 lexInteger,
323 lexString,
324 lexUnknown,
325 // lexIpldType
326 lexBytes,
327 lexCidLink,
328 // lexRefVariant
329 lexRef,
330 lexRefUnion,
331 // other
332 lexBlob,
333 ]),
334 ),
335 })
336 .superRefine(requiredPropertiesRefinement);
337type LexObjectType = z.ZodObject<{
338 type: z.ZodLiteral<"object">;
339 description: z.ZodOptional<z.ZodString>;
340 required: z.ZodOptional<z.ZodArray<z.ZodString>>;
341 nullable: z.ZodOptional<z.ZodArray<z.ZodString>>;
342 properties: z.ZodRecord<
343 z.ZodString,
344 z.ZodDiscriminatedUnion<
345 [
346 LexArrayType,
347 LexBooleanType,
348 LexIntegerType,
349 LexStringType,
350 LexUnknownType,
351 LexBytesType,
352 LexCidLinkType,
353 LexRefType,
354 LexRefUnionType,
355 LexBlobType,
356 ],
357 "type"
358 >
359 >;
360}, z.core.$strip>;
361export type LexObject = z.infer<LexObjectType>;
362
363// permissions
364// =
365
366const lexPermission: LexPermissionType = z.intersection(
367 z.object({
368 type: z.literal("permission"),
369 resource: z.string().min(1),
370 }),
371 z.record(
372 z.string(),
373 z
374 .union([
375 z.array(z.union([z.string(), z.number().int(), z.boolean()])),
376
377 z.boolean(),
378 z.number().int(),
379 z.string(),
380 ])
381 .optional(),
382 ),
383);
384type LexPermissionType = z.ZodIntersection<
385 z.ZodObject<{
386 type: z.ZodLiteral<"permission">;
387 resource: z.ZodString;
388 }, z.core.$strip>,
389 z.ZodRecord<
390 z.ZodString,
391 z.ZodOptional<
392 z.ZodUnion<
393 readonly [
394 z.ZodArray<
395 z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>
396 >,
397 z.ZodBoolean,
398 z.ZodNumber,
399 z.ZodString,
400 ]
401 >
402 >
403 >
404>;
405export type LexPermission = z.infer<LexPermissionType>;
406
407export const lexPermissionSet: LexPermissionSetType = z.object({
408 type: z.literal("permission-set"),
409 description: z.string().optional(),
410 title: z.string().optional(),
411 "title:lang": lexLang.optional(),
412 detail: z.string().optional(),
413 "detail:lang": lexLang.optional(),
414 permissions: z.array(lexPermission),
415});
416type LexPermissionSetType = z.ZodObject<{
417 type: z.ZodLiteral<"permission-set">;
418 description: z.ZodOptional<z.ZodString>;
419 title: z.ZodOptional<z.ZodString>;
420 "title:lang": z.ZodOptional<LexLangType>;
421 detail: z.ZodOptional<z.ZodString>;
422 "detail:lang": z.ZodOptional<LexLangType>;
423 permissions: z.ZodArray<LexPermissionType>;
424}, z.core.$strip>;
425export type LexPermissionSet = z.infer<LexPermissionSetType>;
426
427// xrpc
428// =
429
430export const lexXrpcParameters: LexXrpcParametersType = z
431 .object({
432 type: z.literal("params"),
433 description: z.string().optional(),
434 required: z.string().array().optional(),
435 properties: z.record(
436 z.string(),
437 z.discriminatedUnion("type", [
438 lexPrimitiveArray,
439
440 // lexPrimitive
441 lexBoolean,
442 lexInteger,
443 lexString,
444 lexUnknown,
445 ]),
446 ),
447 })
448 .superRefine(requiredPropertiesRefinement);
449type LexXrpcParametersType = z.ZodObject<{
450 type: z.ZodLiteral<"params">;
451 description: z.ZodOptional<z.ZodString>;
452 required: z.ZodOptional<z.ZodArray<z.ZodString>>;
453 properties: z.ZodRecord<
454 z.ZodString,
455 z.ZodDiscriminatedUnion<
456 [
457 LexPrimitiveArrayType,
458 LexBooleanType,
459 LexIntegerType,
460 LexStringType,
461 LexUnknownType,
462 ],
463 "type"
464 >
465 >;
466}, z.core.$strip>;
467export type LexXrpcParameters = z.infer<LexXrpcParametersType>;
468
469export const lexXrpcBody: LexXrpcBodyType = z.object({
470 description: z.string().optional(),
471 encoding: z.string(),
472 // @NOTE using discriminatedUnion with a refined schema requires zod >= 4
473 schema: z.union([lexRefVariant, lexObject]).optional(),
474});
475type LexXrpcBodyType = z.ZodObject<{
476 description: z.ZodOptional<z.ZodString>;
477 encoding: z.ZodString;
478 schema: z.ZodOptional<
479 z.ZodUnion<readonly [LexRefVariantType, LexObjectType]>
480 >;
481}, z.core.$strip>;
482export type LexXrpcBody = z.infer<LexXrpcBodyType>;
483
484export const lexXrpcSubscriptionMessage: LexXrpcSubscriptionMessageType = z
485 .object({
486 description: z.string().optional(),
487 // @NOTE using discriminatedUnion with a refined schema requires zod >= 4
488 schema: z.union([lexRefVariant, lexObject]).optional(),
489 });
490type LexXrpcSubscriptionMessageType = z.ZodObject<{
491 description: z.ZodOptional<z.ZodString>;
492 schema: z.ZodOptional<
493 z.ZodUnion<readonly [LexRefVariantType, LexObjectType]>
494 >;
495}, z.core.$strip>;
496export type LexXrpcSubscriptionMessage = z.infer<
497 LexXrpcSubscriptionMessageType
498>;
499
500export const lexXrpcError: LexXrpcErrorType = z.object({
501 name: z.string(),
502 description: z.string().optional(),
503});
504type LexXrpcErrorType = z.ZodObject<{
505 name: z.ZodString;
506 description: z.ZodOptional<z.ZodString>;
507}, z.core.$strip>;
508export type LexXrpcError = z.infer<LexXrpcErrorType>;
509
510export const lexXrpcQuery: LexXrpcQueryType = z.object({
511 type: z.literal("query"),
512 description: z.string().optional(),
513 parameters: lexXrpcParameters.optional(),
514 output: lexXrpcBody.optional(),
515 errors: lexXrpcError.array().optional(),
516});
517type LexXrpcQueryType = z.ZodObject<{
518 type: z.ZodLiteral<"query">;
519 description: z.ZodOptional<z.ZodString>;
520 parameters: z.ZodOptional<LexXrpcParametersType>;
521 output: z.ZodOptional<LexXrpcBodyType>;
522 errors: z.ZodOptional<z.ZodArray<LexXrpcErrorType>>;
523}, z.core.$strip>;
524export type LexXrpcQuery = z.infer<LexXrpcQueryType>;
525
526export const lexXrpcProcedure: LexXrpcProcedureType = z.object({
527 type: z.literal("procedure"),
528 description: z.string().optional(),
529 parameters: lexXrpcParameters.optional(),
530 input: lexXrpcBody.optional(),
531 output: lexXrpcBody.optional(),
532 errors: lexXrpcError.array().optional(),
533});
534type LexXrpcProcedureType = z.ZodObject<{
535 type: z.ZodLiteral<"procedure">;
536 description: z.ZodOptional<z.ZodString>;
537 parameters: z.ZodOptional<LexXrpcParametersType>;
538 input: z.ZodOptional<LexXrpcBodyType>;
539 output: z.ZodOptional<LexXrpcBodyType>;
540 errors: z.ZodOptional<z.ZodArray<LexXrpcErrorType>>;
541}, z.core.$strip>;
542export type LexXrpcProcedure = z.infer<LexXrpcProcedureType>;
543
544export const lexXrpcSubscription: LexXrpcSubscriptionType = z.object({
545 type: z.literal("subscription"),
546 description: z.string().optional(),
547 parameters: lexXrpcParameters.optional(),
548 message: lexXrpcSubscriptionMessage.optional(),
549 errors: lexXrpcError.array().optional(),
550});
551type LexXrpcSubscriptionType = z.ZodObject<{
552 type: z.ZodLiteral<"subscription">;
553 description: z.ZodOptional<z.ZodString>;
554 parameters: z.ZodOptional<LexXrpcParametersType>;
555 message: z.ZodOptional<LexXrpcSubscriptionMessageType>;
556 errors: z.ZodOptional<z.ZodArray<LexXrpcErrorType>>;
557}, z.core.$strip>;
558export type LexXrpcSubscription = z.infer<LexXrpcSubscriptionType>;
559
560// database
561// =
562
563export const lexRecord: LexRecordType = z.object({
564 type: z.literal("record"),
565 description: z.string().optional(),
566 key: z.string().optional(),
567 record: lexObject,
568});
569type LexRecordType = z.ZodObject<{
570 type: z.ZodLiteral<"record">;
571 description: z.ZodOptional<z.ZodString>;
572 key: z.ZodOptional<z.ZodString>;
573 record: LexObjectType;
574}, z.core.$strip>;
575export type LexRecord = z.infer<LexRecordType>;
576
577// core
578// =
579
580// We need to use `z.custom` here because
581// lexXrpcProperty and lexObject are refined
582// `z.union` would work, but it's too slow
583// see #915 for details
584export const lexUserType: LexUserTypeType = z.custom<
585 | LexRecord
586 | LexPermissionSet
587 | LexXrpcQuery
588 | LexXrpcProcedure
589 | LexXrpcSubscription
590 | LexBlob
591 | LexArray
592 | LexToken
593 | LexObject
594 | LexBoolean
595 | LexInteger
596 | LexString
597 | LexBytes
598 | LexCidLink
599 | LexUnknown
600>(
601 (val) => {
602 if (!val || typeof val !== "object") {
603 return false;
604 }
605
606 const obj = val as Record<string, unknown>;
607
608 if (obj["type"] === undefined) {
609 return false;
610 }
611
612 try {
613 switch (obj["type"]) {
614 case "record":
615 lexRecord.parse(val);
616 return true;
617
618 case "permission-set":
619 lexPermissionSet.parse(val);
620 return true;
621
622 case "query":
623 lexXrpcQuery.parse(val);
624 return true;
625 case "procedure":
626 lexXrpcProcedure.parse(val);
627 return true;
628 case "subscription":
629 lexXrpcSubscription.parse(val);
630 return true;
631
632 case "blob":
633 lexBlob.parse(val);
634 return true;
635
636 case "array":
637 lexArray.parse(val);
638 return true;
639 case "token":
640 lexToken.parse(val);
641 return true;
642 case "object":
643 lexObject.parse(val);
644 return true;
645
646 case "boolean":
647 lexBoolean.parse(val);
648 return true;
649 case "integer":
650 lexInteger.parse(val);
651 return true;
652 case "string":
653 lexString.parse(val);
654 return true;
655 case "bytes":
656 lexBytes.parse(val);
657 return true;
658 case "cid-link":
659 lexCidLink.parse(val);
660 return true;
661 case "unknown":
662 lexUnknown.parse(val);
663 return true;
664
665 default:
666 return false;
667 }
668 } catch {
669 return false;
670 }
671 },
672 {
673 error: (val) => {
674 if (!val || typeof val !== "object") {
675 return "Must be an object";
676 }
677
678 if (val["type"] === undefined) {
679 return "Must have a type";
680 }
681
682 if (typeof val["type"] !== "string") {
683 return "Type property must be a string";
684 }
685
686 return `Invalid type: ${
687 val["type"]
688 } must be one of: record, query, procedure, subscription, blob, array, token, object, boolean, integer, string, bytes, cid-link, unknown`;
689 },
690 },
691);
692type LexUserTypeType = z.ZodCustom<
693 | LexRecord
694 | LexPermissionSet
695 | LexXrpcQuery
696 | LexXrpcProcedure
697 | LexXrpcSubscription
698 | LexBlob
699 | LexArray
700 | LexToken
701 | LexObject
702 | LexBoolean
703 | LexInteger
704 | LexString
705 | LexBytes
706 | LexCidLink
707 | LexUnknown
708>;
709export type LexUserType = z.infer<LexUserTypeType>;
710
711export const lexiconDoc: LexiconDocType = z
712 .object({
713 lexicon: z.literal(1),
714 id: z.string().refine(isValidNsid, {
715 message: "Must be a valid NSID",
716 }),
717 revision: z.number().optional(),
718 description: z.string().optional(),
719 defs: z.record(z.string(), lexUserType),
720 })
721 .refine(
722 (doc) => {
723 for (const [defId, def] of Object.entries(doc.defs)) {
724 if (
725 defId !== "main" &&
726 (def.type === "record" ||
727 def.type === "permission-set" ||
728 def.type === "procedure" ||
729 def.type === "query" ||
730 def.type === "subscription")
731 ) {
732 return false;
733 }
734 }
735 return true;
736 },
737 {
738 message:
739 `Records, permission sets, procedures, queries, and subscriptions must be the main definition.`,
740 },
741 );
742type LexiconDocType = z.ZodObject<{
743 lexicon: z.ZodLiteral<1>;
744 id: z.ZodString;
745 revision: z.ZodOptional<z.ZodNumber>;
746 description: z.ZodOptional<z.ZodString>;
747 defs: z.ZodRecord<z.ZodString, LexUserTypeType>;
748}, z.core.$strip>;
749export type LexiconDoc = z.infer<LexiconDocType>;
750
751// helpers
752// =
753
754export function isValidLexiconDoc(v: unknown): v is LexiconDoc {
755 return lexiconDoc.safeParse(v).success;
756}
757
758export function isObj<V>(v: V): v is V & object {
759 return v != null && typeof v === "object";
760}
761
762export type DiscriminatedObject = { $type: string };
763export function isDiscriminatedObject(v: unknown): v is DiscriminatedObject {
764 return isObj(v) && "$type" in v && typeof v.$type === "string";
765}
766
767export function parseLexiconDoc(v: unknown): LexiconDoc {
768 lexiconDoc.parse(v);
769 return v as LexiconDoc;
770}
771
772export type ValidationResult<V = unknown> =
773 | {
774 success: true;
775 value: V;
776 }
777 | {
778 success: false;
779 error: ValidationError;
780 };
781
782export class ValidationError extends Error {}
783export class InvalidLexiconError extends Error {}
784export class LexiconDefNotFoundError extends Error {}