@valbuild/core 0.125.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 (35) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/dist/declarations/src/index.d.ts +3 -1
  3. package/dist/declarations/src/initSchema.d.ts +46 -15
  4. package/dist/declarations/src/initVal.d.ts +108 -0
  5. package/dist/declarations/src/render.d.ts +5 -4
  6. package/dist/declarations/src/schema/array.d.ts +65 -0
  7. package/dist/declarations/src/schema/boolean.d.ts +44 -0
  8. package/dist/declarations/src/schema/code.d.ts +63 -0
  9. package/dist/declarations/src/schema/color.d.ts +54 -0
  10. package/dist/declarations/src/schema/date.d.ts +64 -2
  11. package/dist/declarations/src/schema/datetime.d.ts +87 -2
  12. package/dist/declarations/src/schema/discriminatedUnion.d.ts +166 -0
  13. package/dist/declarations/src/schema/enum.d.ts +122 -0
  14. package/dist/declarations/src/schema/file.d.ts +77 -0
  15. package/dist/declarations/src/schema/image.d.ts +102 -0
  16. package/dist/declarations/src/schema/index.d.ts +42 -2
  17. package/dist/declarations/src/schema/keyOf.d.ts +50 -0
  18. package/dist/declarations/src/schema/literal.d.ts +46 -0
  19. package/dist/declarations/src/schema/number.d.ts +58 -0
  20. package/dist/declarations/src/schema/object.d.ts +63 -0
  21. package/dist/declarations/src/schema/record.d.ts +136 -0
  22. package/dist/declarations/src/schema/richtext.d.ts +78 -0
  23. package/dist/declarations/src/schema/route.d.ts +44 -0
  24. package/dist/declarations/src/schema/string.d.ts +99 -0
  25. package/dist/declarations/src/schema/union.d.ts +54 -91
  26. package/dist/{index-a3846321.cjs.prod.js → index-6db78f3c.cjs.prod.js} +1953 -449
  27. package/dist/{index-daa122af.cjs.dev.js → index-b41b638f.cjs.dev.js} +1953 -449
  28. package/dist/{index-915dd675.esm.js → index-cd25c2dc.esm.js} +1952 -449
  29. package/dist/valbuild-core.cjs.dev.js +3 -2
  30. package/dist/valbuild-core.cjs.prod.js +3 -2
  31. package/dist/valbuild-core.esm.js +1 -1
  32. package/package.json +1 -1
  33. package/patch/dist/valbuild-core-patch.cjs.dev.js +1 -1
  34. package/patch/dist/valbuild-core-patch.cjs.prod.js +1 -1
  35. package/patch/dist/valbuild-core-patch.esm.js +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,120 @@
1
1
  # @valbuild/core
2
2
 
3
+ ## 0.126.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#652](https://github.com/valbuild/val/pull/652) [`f2fe70d`](https://github.com/valbuild/val/commit/f2fe70dab2b65000dfaf289f09c70b4a8291467a) Thanks [@freekh](https://github.com/freekh)! - `s.union` is now `s.discriminatedUnion` and `s.enum`.
8
+
9
+ `s.union` did two unrelated jobs and worked out which one you meant from its
10
+ first argument: a string key meant a tagged union of objects, literal schemas
11
+ meant a set of allowed strings. Those are now two schemas with two names.
12
+
13
+ ```ts
14
+ // A fixed set of strings — presents as a dropdown
15
+ s.enum("primary", "secondary", "ghost"); // Schema<"primary" | "secondary" | "ghost">
16
+
17
+ // One of several object shapes, told apart by a tag field
18
+ s.discriminatedUnion(
19
+ "type",
20
+ s.object({ type: s.literal("hero"), heading: s.string() }),
21
+ s.object({ type: s.literal("quote"), text: s.string() }),
22
+ );
23
+ ```
24
+
25
+ `s.enum` takes the strings directly, so the `s.literal(...)` wrapper is gone.
26
+
27
+ **`s.union` still works** — it is deprecated, and it builds exactly the schema
28
+ above, so nothing has to change today:
29
+
30
+ ```ts
31
+ s.union(s.literal("one"), s.literal("two")); // → s.enum("one", "two")
32
+ s.union("type", pageA, pageB); // → s.discriminatedUnion("type", pageA, pageB)
33
+ ```
34
+
35
+ The two are different kinds of node, and that is the reason for the split. A
36
+ discriminated union is a container: the selected variant's fields are the fields
37
+ being edited, and everything that walks a schema descends through it. An enum is
38
+ a leaf — a string with a closed domain — so nothing recurses into it. Told apart
39
+ only by the shape of `key`, every consumer had to re-derive which one it was
40
+ holding; each now has its own serialized type (`"discriminated-union"` and
41
+ `"enum"`) and Val Studio has a field per kind rather than one field that
42
+ branches.
43
+
44
+ Two behaviour changes fall out of the split, both of them fixes:
45
+
46
+ - A value that is not a string at all now fails an enum's validation with a
47
+ type error. `s.union` of literals only ever checked the value against its
48
+ literals when the value WAS a string, so a number or an object where an enum
49
+ was declared validated clean.
50
+ - An enum field now shows its validation errors in Val Studio where the field
51
+ is opened on its own — the module editor and the canvas's fields column — and
52
+ gets the compact error layout inside an inline list row. It is a leaf now, so
53
+ it goes through the same error rendering as every other leaf field; the string
54
+ union bypassed it and showed nothing in those places.
55
+
56
+ Several latent crashes in the old `s.union` are fixed on the way past, all of
57
+ them cases where it threw a `TypeError` instead of reporting:
58
+
59
+ - A required discriminated union holding `null` now reports a type error rather
60
+ than throwing, and resolving a path underneath a nullable one that is `null`
61
+ gives the error the API promises instead of a crash.
62
+ - `s.literal("")` is a legal discriminator tag, and `s.enum("")` a legal value.
63
+ Both used to be treated as absent by a truthiness check — in path resolution,
64
+ in stega encoding, and in the message that lists a union's valid tags. The
65
+ editor's dropdowns handle them too: an empty value is reserved by the select
66
+ component and had to be mapped around.
67
+ - A variant that omits the discriminator entirely is now reported as the schema
68
+ error it is, instead of throwing while the check looked for it.
69
+ - An enum's value is now indexed for search, like every other string leaf. The
70
+ old string union was never indexed at all, so searching for one of its values
71
+ could not find the field.
72
+ - A nullable discriminated union set to `null` no longer renders a spinner that
73
+ never resolves.
74
+
75
+ `s.discriminatedUnion` also requires at least one variant, as `s.enum` requires
76
+ at least one value: a union with nothing to select is not a thing to write, and
77
+ everything downstream reads the first variant where it needs any.
78
+
79
+ If you read serialized schemas yourself, that is the breaking part: `type` is no
80
+ longer `"union"`, an enum carries `values: string[]` instead of a `key` plus
81
+ `items` of literal schemas, and `UnionSchema` is no longer a class.
82
+ `SerializedUnionSchema`, `SerializedStringUnionSchema`,
83
+ `SerializedObjectUnionSchema` and `UnionSchema` remain as deprecated type
84
+ aliases.
85
+
86
+ ### Patch Changes
87
+
88
+ - [#661](https://github.com/valbuild/val/pull/661) [`171208a`](https://github.com/valbuild/val/commit/171208a20177e68ed5a8b1a6fdaabfe893a6aa5f) Thanks [@freekh](https://github.com/freekh)! - Every schema method now has a worked `@example` in its JSDoc, so hovering it in
89
+ your editor shows what to write.
90
+
91
+ That covers the whole builder surface — `.describe()`, `.validate()`,
92
+ `.nullable()`, `.readonly()`, `.hidden()`, `.preview()`, `.render()`, and the
93
+ per-type methods such as `.minLength()`, `.regexp()`, `.raw()`, `.multiline()`,
94
+ `.from()` / `.to()`, `.remote()`, `.jsonValues()` and `.external()` — as well as
95
+ `c.define()`, `c.json()`, `c.external()` and the `val` helpers (`val.attrs`,
96
+ `val.raw`, `val.unstable_getPath` and friends).
97
+
98
+ One of the examples corrected a real trap: a custom validator returns
99
+ `false | string`, so the natural-looking
100
+
101
+ ```ts
102
+ s.string().validate((val) => val.trim() === val || "No surrounding spaces");
103
+ ```
104
+
105
+ does not type check — `||` yields `true`, and `true` is not one of the two
106
+ answers. Write it as a ternary instead:
107
+
108
+ ```ts
109
+ s.string().validate((val) =>
110
+ val.trim() === val ? false : "No surrounding spaces",
111
+ );
112
+ ```
113
+
114
+ The examples are checked in CI, not just written: one test asks the TypeScript
115
+ checker for the doc each method actually resolves to and fails if it has no
116
+ `@example`, and another compiles every example it finds.
117
+
3
118
  ## 0.125.0
4
119
 
5
120
  ### Minor Changes
@@ -63,7 +63,9 @@ export { type SerializedDateTimeSchema, DateTimeSchema, } from "./schema/datetim
63
63
  export { type SerializedKeyOfSchema, KeyOfSchema } from "./schema/keyOf.js";
64
64
  export { type SerializedRouteSchema, RouteSchema } from "./schema/route.js";
65
65
  export { type SerializedRichTextSchema, RichTextSchema, } from "./schema/richtext.js";
66
- export { type SerializedUnionSchema, UnionSchema, type SerializedStringUnionSchema, type SerializedObjectUnionSchema, } from "./schema/union.js";
66
+ export { type SerializedDiscriminatedUnionSchema, type DiscriminatedUnionSourceOf, DiscriminatedUnionSchema, } from "./schema/discriminatedUnion.js";
67
+ export { type SerializedEnumSchema, EnumSchema } from "./schema/enum.js";
68
+ export { type SerializedUnionSchema, type UnionSchema, type SerializedStringUnionSchema, type SerializedObjectUnionSchema, } from "./schema/union.js";
67
69
  export { type SerializedLiteralSchema, LiteralSchema } from "./schema/literal.js";
68
70
  export { deserializeSchema } from "./schema/deserialize.js";
69
71
  export { type PreviewItem, type ItemPreviewInput, type RecordPreview, type ArrayPreview, type ReifiedPreview, type PreviewScope, previewScope, } from "./preview.js";
@@ -4,6 +4,8 @@ import { object } from "./schema/object.js";
4
4
  import { string } from "./schema/string.js";
5
5
  import { boolean } from "./schema/boolean.js";
6
6
  import { union } from "./schema/union.js";
7
+ import { discriminatedUnion } from "./schema/discriminatedUnion.js";
8
+ import { enumSchema } from "./schema/enum.js";
7
9
  import { richtext } from "./schema/richtext.js";
8
10
  import { image } from "./schema/image.js";
9
11
  import { literal } from "./schema/literal.js";
@@ -67,32 +69,58 @@ export type InitSchema = {
67
69
  */
68
70
  readonly number: typeof number;
69
71
  /**
70
- * Define a union.
72
+ * Define one of several object shapes, told apart by a tag field.
71
73
  *
72
- * @example // union of string literals
73
- * const schema = s.union(s.literal("test"), s.literal("test2"));
74
- * export default c.define("/example.val.ts", schema, "test");
74
+ * The first argument names the field that carries the tag; every object must
75
+ * set it to a distinct `s.literal(...)`. The editor shows a dropdown of the
76
+ * tags and the fields of whichever one is selected.
75
77
  *
76
- * @example // union of string literals
77
- * const schema = s.union("type", s.object({
78
- * type: s.literal("test"),
79
- * value: s.string()
80
- * }), s.object({
81
- * type: s.literal("test2"),
82
- * value: s.string()
83
- * }));
78
+ * @example
79
+ * const schema = s.discriminatedUnion("type",
80
+ * s.object({ type: s.literal("text"), value: s.string() }),
81
+ * s.object({ type: s.literal("image"), value: s.image() }),
82
+ * );
84
83
  * export default c.define("/example.val.ts", schema, {
85
- * type: "test",
84
+ * type: "text",
86
85
  * value: "test"
87
86
  * });
88
87
  *
89
88
  */
89
+ readonly discriminatedUnion: typeof discriminatedUnion;
90
+ /**
91
+ * Define a string that must be one of a fixed set of values.
92
+ *
93
+ * The editor shows a dropdown of the values, in the order they are given.
94
+ *
95
+ * @example
96
+ * const schema = s.enum("primary", "secondary", "ghost");
97
+ * export default c.define("/example.val.ts", schema, "primary");
98
+ *
99
+ */
100
+ readonly enum: typeof enumSchema;
101
+ /**
102
+ * Define a union.
103
+ *
104
+ * @deprecated Use `s.discriminatedUnion` for a tagged union of objects, or
105
+ * `s.enum` for one of a fixed set of strings.
106
+ *
107
+ * @example // was: a union of string literals
108
+ * const schema = s.enum("test", "test2");
109
+ *
110
+ * @example // was: a tagged union of objects
111
+ * const schema = s.discriminatedUnion("type",
112
+ * s.object({ type: s.literal("test"), value: s.string() }),
113
+ * s.object({ type: s.literal("test2"), value: s.string() }),
114
+ * );
115
+ *
116
+ */
90
117
  readonly union: typeof union;
91
118
  /**
92
119
  * Define a rich text.
93
120
  *
94
121
  * @example
95
- * const schema = s.richtext();
122
+ * // Every tag and style is off until the options ask for it.
123
+ * const schema = s.richtext({ h1: true, bold: true });
96
124
  * export default c.define("/example.val.ts", schema, [
97
125
  * { tag: "h1", children: ["Title 1"] },
98
126
  * ]);
@@ -119,6 +147,7 @@ export type InitSchema = {
119
147
  *
120
148
  * @example
121
149
  * // Backed by a gallery: width, height and mimeType live there.
150
+ * import galleryVal from "./gallery.val"; // an s.images() module
122
151
  * const schema = s.image(galleryVal);
123
152
  * export default c.define("/example.val.ts", schema, {
124
153
  * path: "/public/val/example.png",
@@ -346,7 +375,9 @@ export declare function initSchema(): {
346
375
  max?: number;
347
376
  min?: number;
348
377
  }) => import("./index.js").NumberSchema<number>;
349
- union: <Key extends string | import("./schema/index.js").Schema<string>, T extends import("./schema/index.js").Schema<Key extends string ? import("./source/index.js").SourceObject & { [k in Key]: string; } : Key extends import("./schema/index.js").Schema<string> ? string : unknown>[]>(key: Key, ...objects: T) => import("./index.js").UnionSchema<Key, T, T extends import("./schema/index.js").Schema<infer S extends import("./selector/index.js").SelectorSource>[] ? S extends import("./selector/index.js").SelectorSource ? S | (Key extends import("./schema/index.js").Schema<infer K extends import("./selector/index.js").SelectorSource> ? K : never) : never : never>;
378
+ discriminatedUnion: <Key extends string, T extends import("./schema/discriminatedUnion.js").DiscriminatedUnionItems<Key>>(key: Key, ...objects: T) => import("./index.js").DiscriminatedUnionSchema<Key, T, import("./index.js").DiscriminatedUnionSourceOf<Key, T>>;
379
+ enum: <T extends string>(values_0: T, ...values: T[]) => import("./index.js").EnumSchema<T>;
380
+ union: typeof union;
350
381
  richtext: <O extends import("./index.js").RichTextOptions>(options?: O) => import("./index.js").RichTextSchema<O, import("./index.js").RichTextSource<O>>;
351
382
  image: typeof image;
352
383
  literal: <T extends string>(value: T) => import("./index.js").LiteralSchema<T>;
@@ -4,11 +4,71 @@ import { getValPath as getPath } from "./val/index.js";
4
4
  import { json } from "./source/json.js";
5
5
  import { external } from "./source/external.js";
6
6
  export type ContentConstructor = {
7
+ /**
8
+ * Define a content module: a schema, and source that matches it.
9
+ *
10
+ * The first argument is the module's own file path, starting at the root of
11
+ * the project. It has to match the file the call is written in - that is how
12
+ * the Studio, validation and patching find the module again - so a
13
+ * `c.define` is always the default export of one `*.val.ts` file.
14
+ *
15
+ * @example
16
+ * const schema = s.object({ title: s.string() });
17
+ * export default c.define("/content/page.val.ts", schema, {
18
+ * title: "Hello",
19
+ * });
20
+ */
7
21
  define: typeof define;
22
+ /**
23
+ * Put one entry of a `.jsonValues()` record in its own `*.val.json` file,
24
+ * loaded lazily.
25
+ *
26
+ * What is written in the module is the `import()` thunk, not the content: the
27
+ * `.val.ts` stays small, and the runtime, the Studio and validation each work
28
+ * an entry at a time. That is what lets a record or a router hold many
29
+ * thousands of entries.
30
+ *
31
+ * Only for the entries of a record that said `.jsonValues()`.
32
+ *
33
+ * @example
34
+ * import { nextAppRouter } from "../val.config";
35
+ * const schema = s
36
+ * .router(nextAppRouter, s.object({ title: s.string() }))
37
+ * .jsonValues();
38
+ * export default c.define("/app/support/[slug]/page.val.ts", schema, {
39
+ * "/support/faq": c.json(() => import("./content/faq.val.json")),
40
+ * });
41
+ */
8
42
  json: typeof json;
43
+ /**
44
+ * The source of a record whose entries live behind an adapter - a database,
45
+ * an HTTP API, a bucket - instead of in the module.
46
+ *
47
+ * It takes no arguments, and there is nothing to fill in: it says only that
48
+ * the entries are somewhere else. WHICH adapter is the schema's business
49
+ * (`.external("posts")`), and the binding is registered where the app is set
50
+ * up.
51
+ *
52
+ * @example
53
+ * const schema = s.record(s.object({ title: s.string() })).external("posts");
54
+ * export default c.define("/content/posts.val.ts", schema, c.external());
55
+ */
9
56
  external: typeof external;
10
57
  };
11
58
  export type ValConstructor = {
59
+ /**
60
+ * The source path of a value inside a module, or `undefined` for something
61
+ * that did not come from Val.
62
+ *
63
+ * A path is what every Val API addresses content by, so this is the way from
64
+ * a value you are holding back to the thing the Studio, a patch or a
65
+ * validation error names. Unstable: the shape of a path is not part of the
66
+ * public API yet.
67
+ *
68
+ * @example
69
+ * import pageVal from "./page.val";
70
+ * const path = val.unstable_getPath(pageVal); // "/page.val.ts"
71
+ */
12
72
  unstable_getPath: typeof getPath;
13
73
  };
14
74
  export type ConfigDirectory = `/public` | `/public/${string}`;
@@ -39,9 +99,57 @@ export type ValConfig = {
39
99
  };
40
100
  };
41
101
  export type InitVal = {
102
+ /**
103
+ * The content constructor: `c.define` for a module, `c.json` and
104
+ * `c.external` for entries that live outside it.
105
+ *
106
+ * @example
107
+ * const schema = s.object({ title: s.string() });
108
+ * export default c.define("/content/page.val.ts", schema, {
109
+ * title: "Hello",
110
+ * });
111
+ */
42
112
  c: ContentConstructor;
113
+ /**
114
+ * Helpers for working with content your app is already holding, rather than
115
+ * for declaring it.
116
+ *
117
+ * `@valbuild/next` and `@valbuild/tanstack` add to this: `val.attrs` for
118
+ * visual editing, `val.raw` to strip the stega encoding off a string.
119
+ *
120
+ * @example
121
+ * import pageVal from "./page.val";
122
+ * const path = val.unstable_getPath(pageVal);
123
+ */
43
124
  val: ValConstructor;
125
+ /**
126
+ * The schema constructor: one method per type of content Val can hold.
127
+ *
128
+ * @example
129
+ * const schema = s.object({
130
+ * title: s.string().describe("Shown in the browser tab"),
131
+ * image: s.image().nullable(),
132
+ * });
133
+ * export default c.define("/content/page.val.ts", schema, {
134
+ * title: "Hello",
135
+ * image: null,
136
+ * });
137
+ */
44
138
  s: InitSchema;
139
+ /**
140
+ * The config this `initVal` was called with, as the rest of Val reads it.
141
+ *
142
+ * Exported from `val.config.ts` so that the Val API route and the Studio get
143
+ * the same object the content modules were built with.
144
+ *
145
+ * @example
146
+ * import { initVal } from "@valbuild/core";
147
+ * const { s, c, val, config } = initVal({
148
+ * project: "myorg/myproject",
149
+ * files: { directory: "/public/val" },
150
+ * });
151
+ * export { s, c, val, config };
152
+ */
45
153
  config: ValConfig;
46
154
  };
47
155
  export declare const initVal: (config?: ValConfig) => InitVal;
@@ -59,10 +59,11 @@ export type FieldRender = InlineRender;
59
59
  * question, and a disagreement between them is a row you can edit in place but
60
60
  * that "add" navigates away from.
61
61
  *
62
- * A tagged union counts as inline when the union itself declares it OR when
63
- * ANY of its variants does. A page-builder list is `s.array(s.union("type",
64
- * block, block, ...))` and the natural place to write the render is on the
65
- * blocks, one per block type — the union is a dispatch, not something the
62
+ * A discriminated union counts as inline when the union itself declares it OR
63
+ * when ANY of its variants does. A page-builder list is
64
+ * `s.array(s.discriminatedUnion("type", block, block, ...))` and the natural
65
+ * place to write the render is on the blocks, one per block type — the union
66
+ * is a dispatch, not something the
66
67
  * author thinks of as the field. `some` rather than `every` because the row
67
68
  * draws the union's own editor (the tag selector plus the matched variant's
68
69
  * fields), which handles every variant either way: with `every`, adding one
@@ -38,7 +38,47 @@ export declare class ArraySchema<T extends Schema<SelectorSource>, Src extends S
38
38
  private readonly previewInput;
39
39
  private readonly renderInput;
40
40
  constructor(item: T, opt?: boolean, customValidateFunctions?: ((src: Src) => false | string)[], isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, previewInput?: ItemPreviewInput<Src> | null, renderInput?: FieldRender | null);
41
+ /**
42
+ * Describe this field.
43
+ *
44
+ * The description is shown next to the field's label in the Val editor, so
45
+ * it is where you say what an editor needs to know but the field name cannot
46
+ * carry. It also travels in the serialized schema, which is what the AI
47
+ * assistant and the MCP tools read.
48
+ *
49
+ * Pass `null` to clear a description set earlier.
50
+ *
51
+ * @example
52
+ * const schema = s
53
+ * .array(s.string())
54
+ * .describe("Bullet points shown under the heading");
55
+ * export default c.define("/example.val.ts", schema, ["First", "Second"]);
56
+ */
41
57
  describe(description: string | null): ArraySchema<T, Src>;
58
+ /**
59
+ * Add a custom validation rule to this field.
60
+ *
61
+ * The function is called with the field's value and returns `false` when the
62
+ * value is fine, or a STRING with the message to show when it is not. Call it
63
+ * more than once to add more rules — they all run, and every message is
64
+ * reported.
65
+ *
66
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
67
+ * when the value is fine, and `true` is not one of the two answers.
68
+ *
69
+ * Validation runs in the Studio as you type, in `npx val validate` and
70
+ * before a publish.
71
+ *
72
+ * The function is given the whole array. To validate one item, put a
73
+ * `.validate(...)` on the ITEM schema instead — that reports the error on
74
+ * the item's own row.
75
+ *
76
+ * @example
77
+ * const schema = s.array(s.string()).validate((val) =>
78
+ * val.length <= 3 ? false : "At most 3 items",
79
+ * );
80
+ * export default c.define("/example.val.ts", schema, ["First", "Second"]);
81
+ */
42
82
  validate(validationFunction: (src: Src) => false | string): ArraySchema<T, Src>;
43
83
  protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
44
84
  protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
@@ -55,6 +95,25 @@ export declare class ArraySchema<T extends Schema<SelectorSource>, Src extends S
55
95
  * is the item of another container, in search, in references. What its rows
56
96
  * show is the ITEM schema's `preview`, not this. Never how the field is
57
97
  * edited (that is `render`). See `preview.ts`.
98
+ *
99
+ * @example
100
+ * // The preview of the ARRAY, for where it is an item of something else.
101
+ * // What its own rows show is the ITEM schema's preview — see below.
102
+ * const tags = s.array(s.string()).preview(({ val }) => ({
103
+ * title: `${val.length} tags`,
104
+ * }));
105
+ * export default c.define("/example.val.ts", s.record(tags), {
106
+ * "a-post": ["news", "release"],
107
+ * });
108
+ *
109
+ * @example
110
+ * // Rows of an array come from the ITEM's preview, not the array's:
111
+ * const author = s
112
+ * .object({ name: s.string(), role: s.string() })
113
+ * .preview(({ val }) => ({ title: val.name, subtitle: val.role }));
114
+ * export default c.define("/example.val.ts", s.array(author), [
115
+ * { name: "Ada", role: "Engineer" },
116
+ * ]);
58
117
  */
59
118
  preview(select: ItemPreviewInput<Src>): ArraySchema<T, Src>;
60
119
  /**
@@ -63,6 +122,12 @@ export declare class ArraySchema<T extends Schema<SelectorSource>, Src extends S
63
122
  * instead of a preview row that navigates to it.
64
123
  *
65
124
  * Static configuration, not a callback — see `render.ts`.
125
+ *
126
+ * @example
127
+ * const schema = s.record(s.array(s.string()).render({ as: "inline" }));
128
+ * export default c.define("/example.val.ts", schema, {
129
+ * "a-post": ["news", "release"],
130
+ * });
66
131
  */
67
132
  render(input: FieldRender): ArraySchema<T, Src>;
68
133
  }
@@ -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;