Suite of AT Protocol TypeScript libraries built on web standards
1import type { $Typed } from "../core.ts";
2import {
3 Schema,
4 type ValidationResult,
5 type Validator,
6 type ValidatorContext,
7} from "../validation.ts";
8
9export type TypedRefSchemaValidator<V extends { $type?: string } = any> =
10 V extends { $type?: infer T extends string }
11 ? { $type: T } & Validator<V & { $type?: T }>
12 : never;
13
14export type TypedRefGetter<V extends { $type?: string } = any> = () =>
15 TypedRefSchemaValidator<V>;
16
17export type TypedRefSchemaOutput<V extends { $type?: string } = any> = V extends
18 { $type?: infer T extends string } ? $Typed<V, T> : never;
19
20export class TypedRefSchema<V extends { $type?: string } = any> extends Schema<
21 TypedRefSchemaOutput<V>
22> {
23 #getter: TypedRefGetter<V>;
24
25 constructor(getter: TypedRefGetter<V>) {
26 super();
27 this.#getter = getter;
28 }
29
30 get schema(): TypedRefSchemaValidator<V> {
31 const value = this.#getter.call(null);
32
33 this.#getter = throwAlreadyCalled;
34
35 Object.defineProperty(this, "schema", {
36 value,
37 writable: false,
38 enumerable: false,
39 configurable: true,
40 });
41
42 return value;
43 }
44
45 get $type(): TypedRefSchemaOutput<V>["$type"] {
46 return this.schema.$type;
47 }
48
49 validateInContext(
50 input: unknown,
51 ctx: ValidatorContext,
52 ): ValidationResult<TypedRefSchemaOutput<V>> {
53 const result = ctx.validate(input, this.schema);
54 if (!result.success) return result;
55
56 if (result.value.$type !== this.$type) {
57 return ctx.issueInvalidPropertyValue(result.value, "$type", [
58 this.$type,
59 ]);
60 }
61
62 return result as ValidationResult<TypedRefSchemaOutput<V>>;
63 }
64}
65
66function throwAlreadyCalled(): never {
67 throw new Error("TypedRefSchema getter called multiple times");
68}