@valbuild/core 0.124.0 → 0.126.0

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +196 -0
  2. package/dist/declarations/src/getSourcePathFromRoute.d.ts +12 -3
  3. package/dist/declarations/src/index.d.ts +7 -3
  4. package/dist/declarations/src/initSchema.d.ts +46 -15
  5. package/dist/declarations/src/initVal.d.ts +108 -0
  6. package/dist/declarations/src/render.d.ts +5 -4
  7. package/dist/declarations/src/router.d.ts +45 -0
  8. package/dist/declarations/src/schema/array.d.ts +65 -0
  9. package/dist/declarations/src/schema/boolean.d.ts +44 -0
  10. package/dist/declarations/src/schema/code.d.ts +63 -0
  11. package/dist/declarations/src/schema/color.d.ts +54 -0
  12. package/dist/declarations/src/schema/date.d.ts +64 -2
  13. package/dist/declarations/src/schema/datetime.d.ts +87 -2
  14. package/dist/declarations/src/schema/discriminatedUnion.d.ts +166 -0
  15. package/dist/declarations/src/schema/enum.d.ts +122 -0
  16. package/dist/declarations/src/schema/file.d.ts +77 -0
  17. package/dist/declarations/src/schema/image.d.ts +102 -0
  18. package/dist/declarations/src/schema/index.d.ts +42 -2
  19. package/dist/declarations/src/schema/keyOf.d.ts +50 -0
  20. package/dist/declarations/src/schema/literal.d.ts +46 -0
  21. package/dist/declarations/src/schema/number.d.ts +58 -0
  22. package/dist/declarations/src/schema/object.d.ts +63 -0
  23. package/dist/declarations/src/schema/record.d.ts +136 -0
  24. package/dist/declarations/src/schema/richtext.d.ts +78 -0
  25. package/dist/declarations/src/schema/route.d.ts +44 -0
  26. package/dist/declarations/src/schema/string.d.ts +99 -0
  27. package/dist/declarations/src/schema/union.d.ts +54 -91
  28. package/dist/{index-22a40d42.cjs.prod.js → index-6db78f3c.cjs.prod.js} +2161 -423
  29. package/dist/{index-fcd087bd.cjs.dev.js → index-b41b638f.cjs.dev.js} +2161 -423
  30. package/dist/{index-9c14df37.esm.js → index-cd25c2dc.esm.js} +2155 -423
  31. package/dist/valbuild-core.cjs.dev.js +8 -2
  32. package/dist/valbuild-core.cjs.prod.js +8 -2
  33. package/dist/valbuild-core.esm.js +1 -1
  34. package/package.json +1 -1
  35. package/patch/dist/valbuild-core-patch.cjs.dev.js +1 -1
  36. package/patch/dist/valbuild-core-patch.cjs.prod.js +1 -1
  37. package/patch/dist/valbuild-core-patch.esm.js +2 -2
@@ -24,7 +24,41 @@ export declare class BooleanSchema<Src extends boolean | null> extends Schema<Sr
24
24
  private readonly renderInput;
25
25
  private readonly previewInput;
26
26
  constructor(opt?: boolean, customValidateFunctions?: CustomValidateFunction<Src>[], isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, renderInput?: FieldRender | null, previewInput?: ItemPreviewInput<Src> | null);
27
+ /**
28
+ * Describe this field.
29
+ *
30
+ * The description is shown next to the field's label in the Val editor, so
31
+ * it is where you say what an editor needs to know but the field name cannot
32
+ * carry. It also travels in the serialized schema, which is what the AI
33
+ * assistant and the MCP tools read.
34
+ *
35
+ * Pass `null` to clear a description set earlier.
36
+ *
37
+ * @example
38
+ * const schema = s.boolean().describe("Show this item in the main menu");
39
+ * export default c.define("/example.val.ts", schema, true);
40
+ */
27
41
  describe(description: string | null): BooleanSchema<Src>;
42
+ /**
43
+ * Add a custom validation rule to this field.
44
+ *
45
+ * The function is called with the field's value and returns `false` when the
46
+ * value is fine, or a STRING with the message to show when it is not. Call it
47
+ * more than once to add more rules — they all run, and every message is
48
+ * reported.
49
+ *
50
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
51
+ * when the value is fine, and `true` is not one of the two answers.
52
+ *
53
+ * Validation runs in the Studio as you type, in `npx val validate` and
54
+ * before a publish.
55
+ *
56
+ * @example
57
+ * const schema = s.boolean().validate((val) =>
58
+ * val === true ? false : "This has to be on for now",
59
+ * );
60
+ * export default c.define("/example.val.ts", schema, true);
61
+ */
28
62
  validate(validationFunction: (src: Src) => false | string): BooleanSchema<Src>;
29
63
  protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
30
64
  protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
@@ -38,12 +72,22 @@ export declare class BooleanSchema<Src extends boolean | null> extends Schema<Sr
38
72
  * instead of a preview row that navigates to it.
39
73
  *
40
74
  * Static configuration, not a callback — see `render.ts`.
75
+ *
76
+ * @example
77
+ * const schema = s.array(s.boolean().render({ as: "inline" }));
78
+ * export default c.define("/example.val.ts", schema, [true, false]);
41
79
  */
42
80
  render(input: FieldRender): BooleanSchema<Src>;
43
81
  /**
44
82
  * How this VALUE is shown where a preview of it is needed — a row in a
45
83
  * sortable list, a reference dropdown, a search hit. Never how the field
46
84
  * itself is edited (that is `render`). See `preview.ts`.
85
+ *
86
+ * @example
87
+ * const schema = s.array(
88
+ * s.boolean().preview(({ val }) => ({ title: val ? "On" : "Off" })),
89
+ * );
90
+ * export default c.define("/example.val.ts", schema, [true, false]);
47
91
  */
48
92
  preview(select: ItemPreviewInput<Src>): BooleanSchema<Src>;
49
93
  protected executePreviewItem(src: NonNullable<Src>): PreviewItem | null;
@@ -55,7 +55,52 @@ export declare class CodeSchema<Src extends string | null> extends Schema<Src> {
55
55
  private readonly renderInput;
56
56
  private readonly previewInput;
57
57
  constructor(options?: CodeOptions | undefined, opt?: boolean, customValidateFunctions?: CustomValidateFunction<Src>[], isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, renderInput?: FieldRender | null, previewInput?: ItemPreviewInput<Src> | null);
58
+ /**
59
+ * Describe this field.
60
+ *
61
+ * The description is shown next to the field's label in the Val editor, so
62
+ * it is where you say what an editor needs to know but the field name cannot
63
+ * carry. It also travels in the serialized schema, which is what the AI
64
+ * assistant and the MCP tools read.
65
+ *
66
+ * Pass `null` to clear a description set earlier.
67
+ *
68
+ * @example
69
+ * const schema = s
70
+ * .code({ language: "css" })
71
+ * .describe("Injected into the page head — keep it small");
72
+ * export default c.define(
73
+ * "/example.val.ts",
74
+ * schema,
75
+ * ".hero { color: red; }",
76
+ * );
77
+ */
58
78
  describe(description: string | null): CodeSchema<Src>;
79
+ /**
80
+ * Add a custom validation rule to this field.
81
+ *
82
+ * The function is called with the field's value and returns `false` when the
83
+ * value is fine, or a STRING with the message to show when it is not. Call it
84
+ * more than once to add more rules — they all run, and every message is
85
+ * reported.
86
+ *
87
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
88
+ * when the value is fine, and `true` is not one of the two answers.
89
+ *
90
+ * Validation runs in the Studio as you type, in `npx val validate` and
91
+ * before a publish.
92
+ *
93
+ * @example
94
+ * const schema = s.code({ language: "json" }).validate((val) => {
95
+ * try {
96
+ * JSON.parse(val);
97
+ * return false;
98
+ * } catch {
99
+ * return "Must be valid JSON";
100
+ * }
101
+ * });
102
+ * export default c.define("/example.val.ts", schema, '{ "a": 1 }');
103
+ */
59
104
  validate(validationFunction: (src: Src) => false | string): CodeSchema<Src>;
60
105
  protected executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
61
106
  protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
@@ -69,12 +114,30 @@ export declare class CodeSchema<Src extends string | null> extends Schema<Src> {
69
114
  * instead of a preview row that navigates to it.
70
115
  *
71
116
  * Static configuration, not a callback — see `render.ts`.
117
+ *
118
+ * @example
119
+ * const schema = s.array(
120
+ * s.code({ language: "css" }).render({ as: "inline" }),
121
+ * );
122
+ * export default c.define("/example.val.ts", schema, [
123
+ * ".hero { color: red; }",
124
+ * ]);
72
125
  */
73
126
  render(input: FieldRender): CodeSchema<Src>;
74
127
  /**
75
128
  * How this VALUE is shown where a preview of it is needed — a row in a
76
129
  * sortable list, a reference dropdown, a search hit. Never how the field
77
130
  * itself is edited (that is `render`). See `preview.ts`.
131
+ *
132
+ * @example
133
+ * const schema = s.array(
134
+ * s.code({ language: "css" }).preview(({ val }) => ({
135
+ * title: val.split("\n")[0],
136
+ * })),
137
+ * );
138
+ * export default c.define("/example.val.ts", schema, [
139
+ * ".hero { color: red; }",
140
+ * ]);
78
141
  */
79
142
  preview(select: ItemPreviewInput<Src>): CodeSchema<Src>;
80
143
  protected executePreviewItem(src: NonNullable<Src>): PreviewItem | null;
@@ -50,7 +50,47 @@ export declare class ColorSchema<Src extends string | null> extends Schema<Src>
50
50
  private readonly renderInput;
51
51
  private readonly previewInput;
52
52
  constructor(options?: ColorOptions | undefined, opt?: boolean, customValidateFunctions?: CustomValidateFunction<Src>[], isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, renderInput?: FieldRender | null, previewInput?: ItemPreviewInput<Src> | null);
53
+ /**
54
+ * Describe this field.
55
+ *
56
+ * The description is shown next to the field's label in the Val editor, so
57
+ * it is where you say what an editor needs to know but the field name cannot
58
+ * carry. It also travels in the serialized schema, which is what the AI
59
+ * assistant and the MCP tools read.
60
+ *
61
+ * Pass `null` to clear a description set earlier.
62
+ *
63
+ * @example
64
+ * const schema = s.color().describe("Background of the hero section");
65
+ * export default c.define(
66
+ * "/example.val.ts",
67
+ * schema,
68
+ * "hsl(217.22 91.22% 59.8%)",
69
+ * );
70
+ */
53
71
  describe(description: string | null): ColorSchema<Src>;
72
+ /**
73
+ * Add a custom validation rule to this field.
74
+ *
75
+ * The function is called with the field's value and returns `false` when the
76
+ * value is fine, or a STRING with the message to show when it is not. Call it
77
+ * more than once to add more rules — they all run, and every message is
78
+ * reported.
79
+ *
80
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
81
+ * when the value is fine, and `true` is not one of the two answers.
82
+ *
83
+ * Validation runs in the Studio as you type, in `npx val validate` and
84
+ * before a publish.
85
+ *
86
+ * @example
87
+ * const schema = s
88
+ * .color({ format: "hex" })
89
+ * .validate((val) =>
90
+ * val === val.toLowerCase() ? false : "Write hex in lower case",
91
+ * );
92
+ * export default c.define("/example.val.ts", schema, "#3b82f6");
93
+ */
54
94
  validate(validationFunction: (src: Src) => false | string): ColorSchema<Src>;
55
95
  protected executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
56
96
  protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
@@ -64,12 +104,26 @@ export declare class ColorSchema<Src extends string | null> extends Schema<Src>
64
104
  * instead of a preview row that navigates to it.
65
105
  *
66
106
  * Static configuration, not a callback — see `render.ts`.
107
+ *
108
+ * @example
109
+ * const schema = s.array(s.color().render({ as: "inline" }));
110
+ * export default c.define("/example.val.ts", schema, [
111
+ * "hsl(217.22 91.22% 59.8%)",
112
+ * ]);
67
113
  */
68
114
  render(input: FieldRender): ColorSchema<Src>;
69
115
  /**
70
116
  * How this VALUE is shown where a preview of it is needed — a row in a
71
117
  * sortable list, a reference dropdown, a search hit. Never how the field
72
118
  * itself is edited (that is `render`). See `preview.ts`.
119
+ *
120
+ * @example
121
+ * const schema = s.array(
122
+ * s.color().preview(({ val }) => ({ title: val })),
123
+ * );
124
+ * export default c.define("/example.val.ts", schema, [
125
+ * "hsl(217.22 91.22% 59.8%)",
126
+ * ]);
73
127
  */
74
128
  preview(select: ItemPreviewInput<Src>): ColorSchema<Src>;
75
129
  protected executePreviewItem(src: NonNullable<Src>): PreviewItem | null;
@@ -9,14 +9,14 @@ type DateOptions = {
9
9
  * Validate that the date is this date or after (inclusive).
10
10
  *
11
11
  * @example
12
- * 2021-01-01
12
+ * "2021-01-01"
13
13
  */
14
14
  from?: string;
15
15
  /**
16
16
  * Validate that the date is this date or before (inclusive).
17
17
  *
18
18
  * @example
19
- * 2021-01-01
19
+ * "2021-01-01"
20
20
  */
21
21
  to?: string;
22
22
  };
@@ -43,11 +43,63 @@ export declare class DateSchema<Src extends string | null> extends Schema<Src> {
43
43
  private readonly renderInput;
44
44
  private readonly previewInput;
45
45
  constructor(options?: DateOptions | undefined, opt?: boolean, customValidateFunctions?: CustomValidateFunction<Src>[], isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, renderInput?: FieldRender | null, previewInput?: ItemPreviewInput<Src> | null);
46
+ /**
47
+ * Describe this field.
48
+ *
49
+ * The description is shown next to the field's label in the Val editor, so
50
+ * it is where you say what an editor needs to know but the field name cannot
51
+ * carry. It also travels in the serialized schema, which is what the AI
52
+ * assistant and the MCP tools read.
53
+ *
54
+ * Pass `null` to clear a description set earlier.
55
+ *
56
+ * @example
57
+ * const schema = s.date().describe("First day the campaign is live");
58
+ * export default c.define("/example.val.ts", schema, "2025-06-01");
59
+ */
46
60
  describe(description: string | null): DateSchema<Src>;
61
+ /**
62
+ * Add a custom validation rule to this field.
63
+ *
64
+ * The function is called with the field's value and returns `false` when the
65
+ * value is fine, or a STRING with the message to show when it is not. Call it
66
+ * more than once to add more rules — they all run, and every message is
67
+ * reported.
68
+ *
69
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
70
+ * when the value is fine, and `true` is not one of the two answers.
71
+ *
72
+ * Validation runs in the Studio as you type, in `npx val validate` and
73
+ * before a publish.
74
+ *
75
+ * @example
76
+ * const schema = s.date().validate((val) =>
77
+ * val >= "2025-01-01" ? false : "Must be in 2025 or later",
78
+ * );
79
+ * export default c.define("/example.val.ts", schema, "2025-06-01");
80
+ */
47
81
  validate(validationFunction: (src: Src) => false | string): DateSchema<Src>;
48
82
  protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
49
83
  protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
84
+ /**
85
+ * Validate that the date is this date or after (inclusive).
86
+ *
87
+ * Written as `YYYY-MM-DD`, the same shape the value has.
88
+ *
89
+ * @example
90
+ * const schema = s.date().from("2025-01-01");
91
+ * export default c.define("/example.val.ts", schema, "2025-06-01");
92
+ */
50
93
  from(from: string): DateSchema<Src>;
94
+ /**
95
+ * Validate that the date is this date or before (inclusive).
96
+ *
97
+ * Written as `YYYY-MM-DD`, the same shape the value has.
98
+ *
99
+ * @example
100
+ * const schema = s.date().from("2025-01-01").to("2025-12-31");
101
+ * export default c.define("/example.val.ts", schema, "2025-06-01");
102
+ */
51
103
  to(to: string): DateSchema<Src>;
52
104
  nullable(): DateSchema<Src | null>;
53
105
  readonly(isReadonly?: boolean): DateSchema<Src>;
@@ -59,12 +111,22 @@ export declare class DateSchema<Src extends string | null> extends Schema<Src> {
59
111
  * instead of a preview row that navigates to it.
60
112
  *
61
113
  * Static configuration, not a callback — see `render.ts`.
114
+ *
115
+ * @example
116
+ * const schema = s.array(s.date().render({ as: "inline" }));
117
+ * export default c.define("/example.val.ts", schema, ["2025-06-01"]);
62
118
  */
63
119
  render(input: FieldRender): DateSchema<Src>;
64
120
  /**
65
121
  * How this VALUE is shown where a preview of it is needed — a row in a
66
122
  * sortable list, a reference dropdown, a search hit. Never how the field
67
123
  * itself is edited (that is `render`). See `preview.ts`.
124
+ *
125
+ * @example
126
+ * const schema = s.array(
127
+ * s.date().preview(({ val }) => ({ title: val })),
128
+ * );
129
+ * export default c.define("/example.val.ts", schema, ["2025-06-01"]);
68
130
  */
69
131
  preview(select: ItemPreviewInput<Src>): DateSchema<Src>;
70
132
  protected executePreviewItem(src: NonNullable<Src>): PreviewItem | null;
@@ -11,7 +11,7 @@ type DateTimeOptions = {
11
11
  * Accepts any ISO 8601 datetime string parseable by `Date.parse`.
12
12
  *
13
13
  * @example
14
- * 2021-01-01T00:00:00Z
14
+ * "2021-01-01T00:00:00Z"
15
15
  */
16
16
  from?: string;
17
17
  /**
@@ -20,7 +20,7 @@ type DateTimeOptions = {
20
20
  * Accepts any ISO 8601 datetime string parseable by `Date.parse`.
21
21
  *
22
22
  * @example
23
- * 2021-12-31T23:59:59Z
23
+ * "2021-12-31T23:59:59Z"
24
24
  */
25
25
  to?: string;
26
26
  };
@@ -47,11 +47,82 @@ export declare class DateTimeSchema<Src extends string | null> extends Schema<Sr
47
47
  private readonly renderInput;
48
48
  private readonly previewInput;
49
49
  constructor(options?: DateTimeOptions | undefined, opt?: boolean, customValidateFunctions?: CustomValidateFunction<Src>[], isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, renderInput?: FieldRender | null, previewInput?: ItemPreviewInput<Src> | null);
50
+ /**
51
+ * Describe this field.
52
+ *
53
+ * The description is shown next to the field's label in the Val editor, so
54
+ * it is where you say what an editor needs to know but the field name cannot
55
+ * carry. It also travels in the serialized schema, which is what the AI
56
+ * assistant and the MCP tools read.
57
+ *
58
+ * Pass `null` to clear a description set earlier.
59
+ *
60
+ * @example
61
+ * const schema = s.datetime().describe("When the article goes live");
62
+ * export default c.define(
63
+ * "/example.val.ts",
64
+ * schema,
65
+ * "2025-06-01T09:00:00.000Z",
66
+ * );
67
+ */
50
68
  describe(description: string | null): DateTimeSchema<Src>;
69
+ /**
70
+ * Add a custom validation rule to this field.
71
+ *
72
+ * The function is called with the field's value and returns `false` when the
73
+ * value is fine, or a STRING with the message to show when it is not. Call it
74
+ * more than once to add more rules — they all run, and every message is
75
+ * reported.
76
+ *
77
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
78
+ * when the value is fine, and `true` is not one of the two answers.
79
+ *
80
+ * Validation runs in the Studio as you type, in `npx val validate` and
81
+ * before a publish.
82
+ *
83
+ * @example
84
+ * const schema = s.datetime().validate((val) =>
85
+ * val.endsWith("Z") ? false : "Must be in UTC",
86
+ * );
87
+ * export default c.define(
88
+ * "/example.val.ts",
89
+ * schema,
90
+ * "2025-06-01T09:00:00.000Z",
91
+ * );
92
+ */
51
93
  validate(validationFunction: (src: Src) => false | string): DateTimeSchema<Src>;
52
94
  protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
53
95
  protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
96
+ /**
97
+ * Validate that the datetime is this datetime or after (inclusive).
98
+ *
99
+ * Any ISO 8601 datetime string `Date.parse` understands.
100
+ *
101
+ * @example
102
+ * const schema = s.datetime().from("2025-01-01T00:00:00Z");
103
+ * export default c.define(
104
+ * "/example.val.ts",
105
+ * schema,
106
+ * "2025-06-01T09:00:00.000Z",
107
+ * );
108
+ */
54
109
  from(from: string): DateTimeSchema<Src>;
110
+ /**
111
+ * Validate that the datetime is this datetime or before (inclusive).
112
+ *
113
+ * Any ISO 8601 datetime string `Date.parse` understands.
114
+ *
115
+ * @example
116
+ * const schema = s
117
+ * .datetime()
118
+ * .from("2025-01-01T00:00:00Z")
119
+ * .to("2025-12-31T23:59:59Z");
120
+ * export default c.define(
121
+ * "/example.val.ts",
122
+ * schema,
123
+ * "2025-06-01T09:00:00.000Z",
124
+ * );
125
+ */
55
126
  to(to: string): DateTimeSchema<Src>;
56
127
  nullable(): DateTimeSchema<Src | null>;
57
128
  readonly(isReadonly?: boolean): DateTimeSchema<Src>;
@@ -63,12 +134,26 @@ export declare class DateTimeSchema<Src extends string | null> extends Schema<Sr
63
134
  * instead of a preview row that navigates to it.
64
135
  *
65
136
  * Static configuration, not a callback — see `render.ts`.
137
+ *
138
+ * @example
139
+ * const schema = s.array(s.datetime().render({ as: "inline" }));
140
+ * export default c.define("/example.val.ts", schema, [
141
+ * "2025-06-01T09:00:00.000Z",
142
+ * ]);
66
143
  */
67
144
  render(input: FieldRender): DateTimeSchema<Src>;
68
145
  /**
69
146
  * How this VALUE is shown where a preview of it is needed — a row in a
70
147
  * sortable list, a reference dropdown, a search hit. Never how the field
71
148
  * itself is edited (that is `render`). See `preview.ts`.
149
+ *
150
+ * @example
151
+ * const schema = s.array(
152
+ * s.datetime().preview(({ val }) => ({ title: val })),
153
+ * );
154
+ * export default c.define("/example.val.ts", schema, [
155
+ * "2025-06-01T09:00:00.000Z",
156
+ * ]);
72
157
  */
73
158
  preview(select: ItemPreviewInput<Src>): DateTimeSchema<Src>;
74
159
  protected executePreviewItem(src: NonNullable<Src>): PreviewItem | null;
@@ -0,0 +1,166 @@
1
+ import { CustomValidateFunction, Schema, SchemaAssertResult } from "./index.js";
2
+ import { ItemPreviewInput, PreviewItem, ReifiedPreview, PreviewScope } from "../preview.js";
3
+ import { FieldRender } from "../render.js";
4
+ import { SelectorSource } from "../selector/index.js";
5
+ import { SourceObject } from "../source/index.js";
6
+ import { ModuleFilePath, SourcePath } from "../val/index.js";
7
+ import { SerializedObjectSchema } from "./object.js";
8
+ import { ValidationError, ValidationErrors } from "./validation/ValidationError.js";
9
+ export type SerializedDiscriminatedUnionSchema = {
10
+ type: "discriminated-union";
11
+ /** Static layout config, carried whole in the serialized schema — see `render.ts`. */
12
+ render?: FieldRender;
13
+ /** Set when this schema declares a `preview`. The closure itself cannot serialize. */
14
+ preview?: true;
15
+ /** The name of the field every variant tags itself with. */
16
+ key: string;
17
+ items: SerializedObjectSchema[];
18
+ opt: boolean;
19
+ customValidate?: boolean;
20
+ readonly?: boolean;
21
+ hidden?: boolean;
22
+ description?: string;
23
+ };
24
+ /** The variant objects a discriminated union on `Key` may be built from. */
25
+ export type DiscriminatedUnionItem<Key extends string> = Schema<SourceObject & {
26
+ [k in Key]: string;
27
+ }>;
28
+ /**
29
+ * At least one variant, so a union with none cannot be written.
30
+ *
31
+ * Everything downstream reads the first one where it needs any — `emptyOf`
32
+ * takes `items[0]` for the value a new entry starts as — and a union with
33
+ * nothing to select is not a thing an author means. `s.enum` says the same
34
+ * about its values.
35
+ */
36
+ export type DiscriminatedUnionItems<Key extends string> = [
37
+ DiscriminatedUnionItem<Key>,
38
+ ...DiscriminatedUnionItem<Key>[]
39
+ ];
40
+ /** The source type a discriminated union over `T` accepts. */
41
+ export type DiscriminatedUnionSourceOf<Key extends string, T extends DiscriminatedUnionItem<Key>[]> = T extends Schema<infer S>[] ? S extends SelectorSource ? S : never : never;
42
+ /**
43
+ * One of several object shapes, told apart by a tag field.
44
+ *
45
+ * Every variant is an object with `key` set to a distinct `s.literal(...)`, and
46
+ * the value of that field decides which variant a value is. Unlike
47
+ * {@link EnumSchema} this is a CONTAINER: the variant's fields are the fields
48
+ * being edited, and everything that walks the schema tree has to descend
49
+ * through the matching variant.
50
+ */
51
+ export declare class DiscriminatedUnionSchema<Key extends string, T extends DiscriminatedUnionItem<Key>[], Src extends DiscriminatedUnionSourceOf<Key, T> | null> extends Schema<Src> {
52
+ private readonly key;
53
+ private readonly items;
54
+ private readonly opt;
55
+ private readonly customValidateFunctions;
56
+ private readonly isReadonly;
57
+ private readonly isHidden;
58
+ private readonly description?;
59
+ private readonly renderInput;
60
+ private readonly previewInput;
61
+ constructor(key: Key, items: T, opt?: boolean, customValidateFunctions?: CustomValidateFunction<Src>[], isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, renderInput?: FieldRender | null, previewInput?: ItemPreviewInput<Src> | null);
62
+ /**
63
+ * Describe this field.
64
+ *
65
+ * The description is shown next to the field's label in the Val editor, so it is
66
+ * where you say what an editor needs to know but the field name cannot carry. It
67
+ * also travels in the serialized schema, which is what the AI assistant and the
68
+ * MCP tools read.
69
+ *
70
+ * Pass `null` to clear a description set earlier.
71
+ *
72
+ * @example
73
+ * const hero = s.object({ type: s.literal("hero"), title: s.string() });
74
+ * const text = s.object({ type: s.literal("text"), body: s.string() });
75
+ * const schema = s
76
+ * .discriminatedUnion("type", hero, text)
77
+ * .describe("What this section of the page is");
78
+ * export default c.define("/example.val.ts", schema, {
79
+ * type: "hero",
80
+ * title: "Hello",
81
+ * });
82
+ */
83
+ describe(description: string | null): DiscriminatedUnionSchema<Key, T, Src>;
84
+ /**
85
+ * Add a custom validation rule to this field.
86
+ *
87
+ * The function is called with the field's value and returns `false` when the
88
+ * value is fine, or a STRING with the message to show when it is not. Call it
89
+ * more than once to add more rules — they all run, and every message is
90
+ * reported.
91
+ *
92
+ * Validation runs in the Studio as you type, in `npx val validate` and before a
93
+ * publish.
94
+ *
95
+ * The function is given the whole value, whichever variant it took. A rule
96
+ * that only concerns one variant belongs on that variant's object schema.
97
+ *
98
+ * @example
99
+ * const hero = s.object({ type: s.literal("hero"), title: s.string() });
100
+ * const text = s.object({ type: s.literal("text"), body: s.string() });
101
+ * const schema = s
102
+ * .discriminatedUnion("type", hero, text)
103
+ * .validate((val) =>
104
+ * val.type === "hero" && !val.title ? "A hero needs a title" : false,
105
+ * );
106
+ * export default c.define("/example.val.ts", schema, {
107
+ * type: "hero",
108
+ * title: "Hello",
109
+ * });
110
+ */
111
+ validate(validationFunction: (src: Src) => false | string): DiscriminatedUnionSchema<Key, T, Src>;
112
+ protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
113
+ protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
114
+ nullable(): DiscriminatedUnionSchema<Key, T, Src | null>;
115
+ readonly(isReadonly?: boolean): DiscriminatedUnionSchema<Key, T, Src>;
116
+ hidden(isHidden?: boolean): DiscriminatedUnionSchema<Key, T, Src>;
117
+ protected executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
118
+ /**
119
+ * The variant schema a value takes. `null` when the value matches none of
120
+ * them — a structural error `executeValidate` already reports.
121
+ */
122
+ private matchedVariant;
123
+ /**
124
+ * How this field is laid out in the editor when it is the item of an array
125
+ * or record: `{ as: "inline" }` renders the field itself inside each row,
126
+ * instead of a preview row that navigates to it.
127
+ *
128
+ * Static configuration, not a callback — see `render.ts`.
129
+ *
130
+ * @example
131
+ * // A page builder: the row draws the tag selector and the matched
132
+ * // variant's fields.
133
+ * const hero = s.object({ type: s.literal("hero"), title: s.string() });
134
+ * const text = s.object({ type: s.literal("text"), body: s.string() });
135
+ * const block = s.discriminatedUnion("type", hero, text).render({ as: "inline" });
136
+ * export default c.define("/example.val.ts", s.array(block), [
137
+ * { type: "hero", title: "Hello" },
138
+ * ]);
139
+ */
140
+ render(input: FieldRender): DiscriminatedUnionSchema<Key, T, Src>;
141
+ /**
142
+ * How this VALUE is shown where a preview of it is needed — a row in a
143
+ * sortable list, a reference dropdown, a search hit. Never how the field
144
+ * itself is edited (that is `render`). See `preview.ts`.
145
+ *
146
+ * Without one of its own, a discriminated union previews as the VARIANT the
147
+ * value takes — declare `preview` on the member objects and the union
148
+ * dispatches.
149
+ *
150
+ * @example
151
+ * const hero = s.object({ type: s.literal("hero"), title: s.string() });
152
+ * const text = s.object({ type: s.literal("text"), body: s.string() });
153
+ * const block = s
154
+ * .discriminatedUnion("type", hero, text)
155
+ * .preview(({ val }) => ({ title: val.type }));
156
+ * export default c.define("/example.val.ts", s.array(block), [
157
+ * { type: "hero", title: "Hello" },
158
+ * ]);
159
+ */
160
+ preview(select: ItemPreviewInput<Src>): DiscriminatedUnionSchema<Key, T, Src>;
161
+ protected executePreviewItem(src: NonNullable<Src>): PreviewItem | null;
162
+ protected declaresItemPreview(): boolean;
163
+ protected executeSerialize(): SerializedDiscriminatedUnionSchema;
164
+ protected executePreview(sourcePath: SourcePath | ModuleFilePath, src: Src, scope?: PreviewScope): ReifiedPreview;
165
+ }
166
+ export declare const discriminatedUnion: <Key extends string, T extends DiscriminatedUnionItems<Key>>(key: Key, ...objects: T) => DiscriminatedUnionSchema<Key, T, DiscriminatedUnionSourceOf<Key, T>>;