@valbuild/core 0.125.0 → 0.127.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 (42) hide show
  1. package/CHANGELOG.md +299 -0
  2. package/dist/declarations/src/index.d.ts +8 -2
  3. package/dist/declarations/src/initSchema.d.ts +62 -15
  4. package/dist/declarations/src/initVal.d.ts +108 -0
  5. package/dist/declarations/src/locale.d.ts +55 -0
  6. package/dist/declarations/src/render.d.ts +5 -4
  7. package/dist/declarations/src/schema/array.d.ts +69 -0
  8. package/dist/declarations/src/schema/boolean.d.ts +44 -0
  9. package/dist/declarations/src/schema/code.d.ts +63 -0
  10. package/dist/declarations/src/schema/color.d.ts +54 -0
  11. package/dist/declarations/src/schema/date.d.ts +64 -2
  12. package/dist/declarations/src/schema/datetime.d.ts +87 -2
  13. package/dist/declarations/src/schema/declaredKeys.d.ts +37 -0
  14. package/dist/declarations/src/schema/discriminatedUnion.d.ts +170 -0
  15. package/dist/declarations/src/schema/discriminatedUnionBranch.d.ts +20 -0
  16. package/dist/declarations/src/schema/enum.d.ts +131 -0
  17. package/dist/declarations/src/schema/file.d.ts +77 -0
  18. package/dist/declarations/src/schema/image.d.ts +102 -0
  19. package/dist/declarations/src/schema/index.d.ts +155 -2
  20. package/dist/declarations/src/schema/keyOf.d.ts +50 -0
  21. package/dist/declarations/src/schema/literal.d.ts +48 -0
  22. package/dist/declarations/src/schema/locale.d.ts +132 -0
  23. package/dist/declarations/src/schema/number.d.ts +58 -0
  24. package/dist/declarations/src/schema/object.d.ts +70 -0
  25. package/dist/declarations/src/schema/record.d.ts +187 -1
  26. package/dist/declarations/src/schema/richtext.d.ts +78 -0
  27. package/dist/declarations/src/schema/route.d.ts +44 -0
  28. package/dist/declarations/src/schema/settings.d.ts +54 -1
  29. package/dist/declarations/src/schema/string.d.ts +99 -0
  30. package/dist/declarations/src/schema/union.d.ts +54 -91
  31. package/dist/declarations/src/schema/validation/ValidationFix.d.ts +1 -1
  32. package/dist/declarations/src/source/settings.d.ts +51 -0
  33. package/dist/{index-915dd675.esm.js → index-5a6e6398.esm.js} +3057 -573
  34. package/dist/{index-daa122af.cjs.dev.js → index-889ac441.cjs.dev.js} +3064 -573
  35. package/dist/{index-a3846321.cjs.prod.js → index-d44deccb.cjs.prod.js} +3064 -573
  36. package/dist/valbuild-core.cjs.dev.js +9 -2
  37. package/dist/valbuild-core.cjs.prod.js +9 -2
  38. package/dist/valbuild-core.esm.js +1 -1
  39. package/package.json +1 -1
  40. package/patch/dist/valbuild-core-patch.cjs.dev.js +1 -1
  41. package/patch/dist/valbuild-core-patch.cjs.prod.js +1 -1
  42. package/patch/dist/valbuild-core-patch.esm.js +2 -2
@@ -11,18 +11,20 @@ import { SerializedObjectSchema } from "./object.js";
11
11
  import { SerializedRecordSchema } from "./record.js";
12
12
  import { SerializedRichTextSchema } from "./richtext.js";
13
13
  import { RawString, SerializedStringSchema } from "./string.js";
14
- import { SerializedUnionSchema } from "./union.js";
14
+ import { SerializedDiscriminatedUnionSchema } from "./discriminatedUnion.js";
15
+ import { SerializedEnumSchema } from "./enum.js";
15
16
  import { SerializedCodeSchema } from "./code.js";
16
17
  import { SerializedColorSchema } from "./color.js";
17
18
  import { SerializedDateSchema } from "./date.js";
18
19
  import { SerializedDateTimeSchema } from "./datetime.js";
20
+ import { SerializedLocaleSchema } from "./locale.js";
19
21
  import { SerializedRouteSchema } from "./route.js";
20
22
  import { SerializedSettingsSchema } from "./settings.js";
21
23
  import { ValidationError, ValidationErrors } from "./validation/ValidationError.js";
22
24
  import { FileSource } from "../source/media.js";
23
25
  import { GenericRichTextSourceNode, RichTextSource } from "../source/richtext.js";
24
26
  import { ReifiedPreview, PreviewScope, PreviewItem } from "../preview.js";
25
- export type SerializedSchema = SerializedStringSchema | SerializedLiteralSchema | SerializedBooleanSchema | SerializedNumberSchema | SerializedObjectSchema | SerializedArraySchema | SerializedUnionSchema | SerializedRichTextSchema | SerializedRecordSchema | SerializedKeyOfSchema | SerializedFileSchema | SerializedDateSchema | SerializedDateTimeSchema | SerializedColorSchema | SerializedCodeSchema | SerializedRouteSchema | SerializedSettingsSchema | SerializedImageSchema;
27
+ export type SerializedSchema = SerializedStringSchema | SerializedLiteralSchema | SerializedBooleanSchema | SerializedNumberSchema | SerializedObjectSchema | SerializedArraySchema | SerializedDiscriminatedUnionSchema | SerializedEnumSchema | SerializedRichTextSchema | SerializedRecordSchema | SerializedKeyOfSchema | SerializedFileSchema | SerializedDateSchema | SerializedDateTimeSchema | SerializedColorSchema | SerializedCodeSchema | SerializedRouteSchema | SerializedLocaleSchema | SerializedSettingsSchema | SerializedImageSchema;
26
28
  type Primitives = number | string | boolean | null | FileSource;
27
29
  export type AssertError = {
28
30
  message: string;
@@ -73,6 +75,88 @@ export declare abstract class Schema<Src extends SelectorSource> {
73
75
  * against the real instance. Structural errors publish first; these merge in.
74
76
  */
75
77
  protected abstract executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
78
+ /**
79
+ * Whether this node is a locale — the field that MARKS a scope, `s.locale()`.
80
+ *
81
+ * Only `LocaleSchema` overrides this. A base implementation rather than an
82
+ * abstract one on purpose: every other schema class answers no, and a
83
+ * question every class had to answer would be twenty edits for one yes.
84
+ *
85
+ * Not the same question as {@link opensLocaleScope}: a locale field marks the
86
+ * scope, the object AROUND it is the scope. See `localeScope.ts`.
87
+ */
88
+ protected isLocaleField(): boolean;
89
+ /**
90
+ * Whether this node OPENS a locale scope, and what opens it.
91
+ *
92
+ * An object with a `s.locale()` field opens one; so does a record keyed by
93
+ * `s.locale()`. Everything below such a node is in that one language, which
94
+ * is why a scope may not contain another — see `localeScope.ts` for the rule
95
+ * and the reason it is validated rather than typed.
96
+ */
97
+ protected opensLocaleScope(): "field" | "key" | null;
98
+ /**
99
+ * The child schemas a locale scope reaches, by the path segment that reaches
100
+ * them.
101
+ *
102
+ * A structural walk that deliberately does NOT go through `executeSerialize`:
103
+ * serializing a subtree at every node to ask a question about its shape is
104
+ * quadratic, and this is asked during validation. `*` stands for the item of
105
+ * an array or record, which has no name of its own.
106
+ */
107
+ protected localeScopeChildren(): {
108
+ key: string;
109
+ schema: Schema<SelectorSource>;
110
+ }[];
111
+ /**
112
+ * Every locale scope opened strictly BELOW this node, by path.
113
+ *
114
+ * Stops descending at each one it finds, so a scope three deep is reported
115
+ * once, by the scope immediately enclosing it, rather than by every ancestor.
116
+ */
117
+ protected localeScopesBelow(prefix?: string[]): {
118
+ path: string[];
119
+ kind: "field" | "key";
120
+ }[];
121
+ /**
122
+ * The scope rule, as errors at this node — or `false` if it is not broken.
123
+ *
124
+ * Two things are wrong and both are wrong in the SCHEMA rather than in the
125
+ * content, so both are `schemaError`s: an object with two locale fields (the
126
+ * subtree below it would be in two languages at once), and a locale scope
127
+ * inside another (the inner one would silently override the outer for part
128
+ * of a subtree that is supposed to be one language throughout).
129
+ *
130
+ * Validated rather than typed on purpose. Expressing "no scope below this
131
+ * one" as a constraint means threading it through every schema class's type
132
+ * parameter, and the errors that fall out of a recursive constraint like
133
+ * that name the whole tree — an unrelated typo in a `.val.ts` would print
134
+ * pages. See the design notes on the locales PR.
135
+ */
136
+ protected localeScopeErrors(): ValidationError[];
137
+ /**
138
+ * The answer, computed once per schema.
139
+ *
140
+ * Memoised because the question is asked from `executeValidate`, which runs
141
+ * once per SOURCE instance: an array of five hundred blocks asks the item
142
+ * schema five hundred times, and each answer walks the schema below it
143
+ * looking for a nested scope. The answer cannot differ between two instances
144
+ * of the same schema — nothing here reads the source — so the walk is done
145
+ * once and the result handed back after that.
146
+ *
147
+ * The error is still reported at every instance's path. Val's validation
148
+ * errors are keyed by source path and that is where the Studio draws them, so
149
+ * a schema error on a row is a schema error on every row; reporting it on one
150
+ * arbitrary row would put it somewhere nobody was looking.
151
+ */
152
+ private localeScopeErrorsMemo?;
153
+ private computeLocaleScopeErrors;
154
+ /**
155
+ * This node's own `s.locale()` fields, where it is an object that has any.
156
+ *
157
+ * Empty everywhere else, so `localeScopeErrors` can live on the base class.
158
+ */
159
+ protected localeFieldNames(): string[];
76
160
  protected executeCustomValidateFunctions(src: Src, customValidateFunctions: CustomValidateFunction<Src>[], ctx: {
77
161
  path: SourcePath;
78
162
  }): ValidationError[];
@@ -92,6 +176,55 @@ export declare abstract class Schema<Src extends SelectorSource> {
92
176
  * For example, if you have an object schema, you must assert each key / value pair manually.
93
177
  */
94
178
  protected abstract executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
179
+ /**
180
+ * Allow `null` as a value for this field.
181
+ *
182
+ * The editor gets a way to clear the field, and the type of the source
183
+ * widens to include `null` — so consuming code has to handle it.
184
+ *
185
+ * `.validate(...)` may be declared before or after `.nullable()`: the
186
+ * validator is carried over either way. Declared before, its argument is
187
+ * typed as non-null even though `null` can reach it, so guard for it.
188
+ *
189
+ * Implementations MUST carry `customValidateFunctions` over to the new
190
+ * instance. `.nullable()` returns a copy, so dropping them there silently
191
+ * un-declares the user's `.validate(...)` whenever it was written before the
192
+ * `.nullable()` — which is the order most people write it in. Thirteen schema
193
+ * classes passed `[]` here until this was fixed; `nullableCustomValidate.test.ts`
194
+ * pins one instance of every factory on `s` against that, and does not compile
195
+ * until a newly added schema is listed in it.
196
+ *
197
+ * The validators keep running when the value IS null: a nullable schema's
198
+ * validator sees `Src | null` and decides for itself. That is what the
199
+ * classes that never dropped them (string, record, route, file, image) have
200
+ * always done.
201
+ *
202
+ * Carrying them over needs a cast, because `Src` sits in a PARAMETER position
203
+ * of {@link CustomValidateFunction} and so `CustomValidateFunction<Src>[]` is
204
+ * not assignable to `CustomValidateFunction<Src | null>[]`. Widening what the
205
+ * functions can be CALLED with is the intent here rather than something the
206
+ * cast gets away with: `null` is handed to them, and the paragraph above is
207
+ * the behaviour that buys.
208
+ *
209
+ * A validator's parameter type was never a runtime guarantee to begin with.
210
+ * `executeValidate` runs the custom validators BEFORE the structural checks
211
+ * (see {@link CustomValidateFunction}'s callers, e.g. `NumberSchema`, which
212
+ * calls them ahead of its `typeof src !== "number"`), so one can already be
213
+ * called with a value of the wrong type entirely — hand-written content, or
214
+ * a node the Studio's walker reached before its type was checked. That is why
215
+ * {@link executeCustomValidateFunctions} catches what a validator throws and
216
+ * reports it as a `schemaError` instead of letting it escape.
217
+ *
218
+ * @example
219
+ * const schema = s.object({
220
+ * title: s.string(),
221
+ * subtitle: s.string().nullable(),
222
+ * });
223
+ * export default c.define("/example.val.ts", schema, {
224
+ * title: "Hello",
225
+ * subtitle: null,
226
+ * });
227
+ */
95
228
  abstract nullable(): Schema<Src | null>;
96
229
  /**
97
230
  * Mark this field as read-only in the Val editor.
@@ -102,6 +235,16 @@ export declare abstract class Schema<Src extends SelectorSource> {
102
235
  * The flag defaults to `true`, so `.readonly()` and `.readonly(true)` are the
103
236
  * same thing. `.readonly(false)` leaves the field editable, which is what a
104
237
  * schema is anyway - pass it when the decision comes from a variable.
238
+ *
239
+ * @example
240
+ * const schema = s.object({
241
+ * id: s.string().readonly(),
242
+ * title: s.string(),
243
+ * });
244
+ * export default c.define("/example.val.ts", schema, {
245
+ * id: "generated-by-the-build",
246
+ * title: "Hello",
247
+ * });
105
248
  */
106
249
  abstract readonly(isReadonly?: boolean): Schema<Src>;
107
250
  /**
@@ -113,6 +256,16 @@ export declare abstract class Schema<Src extends SelectorSource> {
113
256
  * The flag defaults to `true`, so `.hidden()` and `.hidden(true)` are the
114
257
  * same thing. `.hidden(false)` leaves the field visible, which is what a
115
258
  * schema is anyway - pass it when the decision comes from a variable.
259
+ *
260
+ * @example
261
+ * const schema = s.object({
262
+ * title: s.string(),
263
+ * internalNotes: s.string().hidden(),
264
+ * });
265
+ * export default c.define("/example.val.ts", schema, {
266
+ * title: "Hello",
267
+ * internalNotes: "Not shown in the editor",
268
+ * });
116
269
  */
117
270
  abstract hidden(isHidden?: boolean): Schema<Src>;
118
271
  protected abstract executeSerialize(): SerializedSchema;
@@ -42,7 +42,45 @@ export declare class KeyOfSchema<Sel extends GenericSelector<SourceObject>, Src
42
42
  private readonly renderInput;
43
43
  private readonly previewInput;
44
44
  constructor(schema?: SerializedRefSchema | undefined, sourcePath?: SourcePath | undefined, opt?: boolean, customValidateFunctions?: CustomValidateFunction<Src>[], isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, renderInput?: FieldRender | null, previewInput?: ItemPreviewInput<Src> | null);
45
+ /**
46
+ * Describe this field.
47
+ *
48
+ * The description is shown next to the field's label in the Val editor, so
49
+ * it is where you say what an editor needs to know but the field name cannot
50
+ * carry. It also travels in the serialized schema, which is what the AI
51
+ * assistant and the MCP tools read.
52
+ *
53
+ * Pass `null` to clear a description set earlier.
54
+ *
55
+ * @example
56
+ * import authorsVal from "./authors.val"; // a record module
57
+ * const schema = s.keyOf(authorsVal).describe("Who wrote this post");
58
+ * export default c.define("/example.val.ts", schema, "ada");
59
+ */
45
60
  describe(description: string | null): KeyOfSchema<Sel, 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
+ * import authorsVal from "./authors.val"; // a record module
77
+ * const schema = s
78
+ * .keyOf(authorsVal)
79
+ * .validate((val) =>
80
+ * val === "placeholder" ? "Pick a real author" : false,
81
+ * );
82
+ * export default c.define("/example.val.ts", schema, "ada");
83
+ */
46
84
  validate(validationFunction: (src: Src) => false | string): KeyOfSchema<Sel, Src>;
47
85
  protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
48
86
  protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
@@ -56,12 +94,24 @@ export declare class KeyOfSchema<Sel extends GenericSelector<SourceObject>, Src
56
94
  * instead of a preview row that navigates to it.
57
95
  *
58
96
  * Static configuration, not a callback — see `render.ts`.
97
+ *
98
+ * @example
99
+ * import authorsVal from "./authors.val"; // a record module
100
+ * const schema = s.array(s.keyOf(authorsVal).render({ as: "inline" }));
101
+ * export default c.define("/example.val.ts", schema, ["ada"]);
59
102
  */
60
103
  render(input: FieldRender): KeyOfSchema<Sel, Src>;
61
104
  /**
62
105
  * How this VALUE is shown where a preview of it is needed — a row in a
63
106
  * sortable list, a reference dropdown, a search hit. Never how the field
64
107
  * itself is edited (that is `render`). See `preview.ts`.
108
+ *
109
+ * @example
110
+ * import authorsVal from "./authors.val"; // a record module
111
+ * const schema = s.array(
112
+ * s.keyOf(authorsVal).preview(({ val }) => ({ title: val })),
113
+ * );
114
+ * export default c.define("/example.val.ts", schema, ["ada"]);
65
115
  */
66
116
  preview(select: ItemPreviewInput<Src>): KeyOfSchema<Sel, Src>;
67
117
  protected executePreviewItem(src: NonNullable<Src>): PreviewItem | null;
@@ -25,8 +25,46 @@ export declare class LiteralSchema<Src extends string | null> extends Schema<Src
25
25
  private readonly description?;
26
26
  private readonly renderInput;
27
27
  private readonly previewInput;
28
+ /** Type-only marker: as a record key, this declares the key set. See `LocaleSchema`. */
29
+ readonly __declaresRecordKeys: true;
28
30
  constructor(value: string, opt?: boolean, customValidateFunctions?: CustomValidateFunction<Src>[], isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, renderInput?: FieldRender | null, previewInput?: ItemPreviewInput<Src> | null);
31
+ /**
32
+ * Describe this field.
33
+ *
34
+ * The description is shown next to the field's label in the Val editor, so
35
+ * it is where you say what an editor needs to know but the field name cannot
36
+ * carry. It also travels in the serialized schema, which is what the AI
37
+ * assistant and the MCP tools read.
38
+ *
39
+ * Pass `null` to clear a description set earlier.
40
+ *
41
+ * @example
42
+ * const schema = s
43
+ * .literal("hero")
44
+ * .describe("The block type — it identifies the variant");
45
+ * export default c.define("/example.val.ts", schema, "hero");
46
+ */
29
47
  describe(description: string | null): LiteralSchema<Src>;
48
+ /**
49
+ * Add a custom validation rule to this field.
50
+ *
51
+ * The function is called with the field's value and returns `false` when the
52
+ * value is fine, or a STRING with the message to show when it is not. Call it
53
+ * more than once to add more rules — they all run, and every message is
54
+ * reported.
55
+ *
56
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
57
+ * when the value is fine, and `true` is not one of the two answers.
58
+ *
59
+ * Validation runs in the Studio as you type, in `npx val validate` and
60
+ * before a publish.
61
+ *
62
+ * @example
63
+ * const schema = s.literal("hero").validate((val) =>
64
+ * val === "hero" ? false : "Unknown block type",
65
+ * );
66
+ * export default c.define("/example.val.ts", schema, "hero");
67
+ */
30
68
  validate(validationFunction: (src: Src) => false | string): LiteralSchema<Src>;
31
69
  protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
32
70
  protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
@@ -40,12 +78,22 @@ export declare class LiteralSchema<Src extends string | null> extends Schema<Src
40
78
  * instead of a preview row that navigates to it.
41
79
  *
42
80
  * Static configuration, not a callback — see `render.ts`.
81
+ *
82
+ * @example
83
+ * const schema = s.array(s.literal("hero").render({ as: "inline" }));
84
+ * export default c.define("/example.val.ts", schema, ["hero"]);
43
85
  */
44
86
  render(input: FieldRender): LiteralSchema<Src>;
45
87
  /**
46
88
  * How this VALUE is shown where a preview of it is needed — a row in a
47
89
  * sortable list, a reference dropdown, a search hit. Never how the field
48
90
  * itself is edited (that is `render`). See `preview.ts`.
91
+ *
92
+ * @example
93
+ * const schema = s.array(
94
+ * s.literal("hero").preview(({ val }) => ({ title: val })),
95
+ * );
96
+ * export default c.define("/example.val.ts", schema, ["hero"]);
49
97
  */
50
98
  preview(select: ItemPreviewInput<Src>): LiteralSchema<Src>;
51
99
  protected executePreviewItem(src: NonNullable<Src>): PreviewItem | null;
@@ -0,0 +1,132 @@
1
+ import { Schema, SchemaAssertResult, SerializedSchema } from "./index.js";
2
+ import { ItemPreviewInput, PreviewItem, ReifiedPreview } from "../preview.js";
3
+ import { FieldRender } from "../render.js";
4
+ import { SourcePath } from "../val/index.js";
5
+ import { ValidationError, ValidationErrors } from "./validation/ValidationError.js";
6
+ export type SerializedLocaleSchema = {
7
+ type: "locale";
8
+ /** Static layout config, carried whole in the serialized schema — see `render.ts`. */
9
+ render?: FieldRender;
10
+ /** Set when this schema declares a `preview`. The closure itself cannot serialize. */
11
+ preview?: true;
12
+ opt: boolean;
13
+ customValidate?: boolean;
14
+ readonly?: boolean;
15
+ hidden?: boolean;
16
+ description?: string;
17
+ };
18
+ /**
19
+ * One of the languages the project publishes.
20
+ *
21
+ * The set is NOT here. It is declared in the settings module — `locales.available`
22
+ * — so that adding a language is something the people who write the content can
23
+ * do, and so that one list drives validation, the Studio's picker and the
24
+ * translation notes. This schema knows only that the value is one of them, which
25
+ * is checked cross-module through the `locale:check-locale` fix, exactly as
26
+ * `keyOf` and `route` check theirs.
27
+ *
28
+ * Never stega encoded (see `stegaEncode.ts`): a locale goes into `<html lang>`,
29
+ * into `hreflang` and into `Intl` constructors, none of which survive invisible
30
+ * characters.
31
+ */
32
+ export declare class LocaleSchema<Src extends string | null> extends Schema<Src> {
33
+ private readonly opt;
34
+ private readonly customValidateFunctions;
35
+ private readonly isReadonly;
36
+ private readonly isHidden;
37
+ private readonly description?;
38
+ private readonly renderInput;
39
+ private readonly previewInput;
40
+ /**
41
+ * Type-only marker: as a record key, this schema DECLARES the key set.
42
+ *
43
+ * `declare` so it exists in the type and nowhere at runtime — reading it off
44
+ * an instance gives `undefined`, and nothing should. It is how `record()`
45
+ * knows to type entries as `Item | null`: a language nobody has translated
46
+ * into is a null entry, not an absent key. See `DeclaredKeySet` and
47
+ * `RecordSrcOf`.
48
+ */
49
+ readonly __declaresRecordKeys: true;
50
+ constructor(opt?: boolean, customValidateFunctions?: ((src: Src) => false | string)[], isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, renderInput?: FieldRender | null, previewInput?: ItemPreviewInput<Src> | null);
51
+ /**
52
+ * A note to whoever edits this field, shown under it in the Studio.
53
+ *
54
+ * Pass `null` to clear a description set earlier.
55
+ *
56
+ * @example
57
+ * const schema = s.object({
58
+ * locale: s.locale().describe("The language everything below is written in"),
59
+ * title: s.string(),
60
+ * });
61
+ * export default c.define("/example.val.ts", schema, {
62
+ * locale: "nb-NO",
63
+ * title: "Vinterjakke",
64
+ * });
65
+ */
66
+ describe(description: string | null): LocaleSchema<Src>;
67
+ /**
68
+ * A check of your own, on top of "is this one of the project's languages".
69
+ *
70
+ * Return a message when the value is WRONG and `false` when it is fine — the
71
+ * two answers are a complaint and no complaint, so write the check as a
72
+ * ternary rather than as `ok || "message"`, which returns `true` and `true`
73
+ * is neither.
74
+ *
75
+ * @example
76
+ * const schema = s.object({
77
+ * locale: s.locale().validate((val) =>
78
+ * val === "en-US" ? "This section is not translated into English yet" : false,
79
+ * ),
80
+ * title: s.string(),
81
+ * });
82
+ * export default c.define("/example.val.ts", schema, {
83
+ * locale: "nb-NO",
84
+ * title: "Vinterjakke",
85
+ * });
86
+ */
87
+ validate(validationFunction: (src: Src) => false | string): LocaleSchema<Src>;
88
+ protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
89
+ protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
90
+ nullable(): LocaleSchema<Src | null>;
91
+ readonly(): LocaleSchema<Src>;
92
+ hidden(): LocaleSchema<Src>;
93
+ protected isLocaleField(): boolean;
94
+ protected executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
95
+ /**
96
+ * How this field is laid out where it is the item of an array or a record.
97
+ * Static configuration, not a callback — see `render.ts`.
98
+ *
99
+ * @example
100
+ * const schema = s.array(s.locale().render({ as: "inline" }));
101
+ * export default c.define("/example.val.ts", schema, ["nb-NO", "en-US"]);
102
+ */
103
+ render(input: FieldRender): LocaleSchema<Src>;
104
+ /**
105
+ * How this VALUE is shown where a preview of it is needed. Never how the field
106
+ * itself is edited (that is `render`). See `preview.ts`.
107
+ *
108
+ * @example
109
+ * const schema = s.array(
110
+ * s.locale().preview(({ val }) => ({ title: val })),
111
+ * );
112
+ * export default c.define("/example.val.ts", schema, ["nb-NO", "en-US"]);
113
+ */
114
+ preview(select: ItemPreviewInput<Src>): LocaleSchema<Src>;
115
+ protected executePreviewItem(src: NonNullable<Src>): PreviewItem | null;
116
+ protected declaresItemPreview(): boolean;
117
+ protected executeSerialize(): SerializedSchema;
118
+ protected executePreview(): ReifiedPreview;
119
+ }
120
+ /**
121
+ * Define one of the project's languages.
122
+ *
123
+ * The languages themselves are declared in the settings module, under
124
+ * `locales.available` — this says only that a value is one of them.
125
+ *
126
+ * @example // a field: everything under this object is in this language
127
+ * const schema = s.object({ locale: s.locale(), title: s.string() });
128
+ *
129
+ * @example // a key: one entry per language
130
+ * const schema = s.record(s.locale(), s.object({ title: s.string() }));
131
+ */
132
+ export declare const locale: () => LocaleSchema<string>;
@@ -30,14 +30,62 @@ export declare class NumberSchema<Src extends number | null> extends Schema<Src>
30
30
  private readonly renderInput;
31
31
  private readonly previewInput;
32
32
  constructor(options?: NumberOptions | undefined, opt?: boolean, customValidateFunctions?: CustomValidateFunction<Src>[], isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, renderInput?: FieldRender | null, previewInput?: ItemPreviewInput<Src> | null);
33
+ /**
34
+ * Describe this field.
35
+ *
36
+ * The description is shown next to the field's label in the Val editor, so
37
+ * it is where you say what an editor needs to know but the field name cannot
38
+ * carry. It also travels in the serialized schema, which is what the AI
39
+ * assistant and the MCP tools read.
40
+ *
41
+ * Pass `null` to clear a description set earlier.
42
+ *
43
+ * @example
44
+ * const schema = s.number().describe("How many columns the grid uses");
45
+ * export default c.define("/example.val.ts", schema, 3);
46
+ */
33
47
  describe(description: string | null): NumberSchema<Src>;
48
+ /**
49
+ * Add a custom validation rule to this field.
50
+ *
51
+ * The function is called with the field's value and returns `false` when the
52
+ * value is fine, or a STRING with the message to show when it is not. Call it
53
+ * more than once to add more rules — they all run, and every message is
54
+ * reported.
55
+ *
56
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
57
+ * when the value is fine, and `true` is not one of the two answers.
58
+ *
59
+ * Validation runs in the Studio as you type, in `npx val validate` and
60
+ * before a publish.
61
+ *
62
+ * @example
63
+ * const schema = s.number().validate((val) =>
64
+ * Number.isInteger(val) ? false : "Must be a whole number",
65
+ * );
66
+ * export default c.define("/example.val.ts", schema, 3);
67
+ */
34
68
  validate(validationFunction: (src: Src) => false | string): NumberSchema<Src>;
35
69
  protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
36
70
  protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
37
71
  nullable(): NumberSchema<Src | null>;
38
72
  readonly(isReadonly?: boolean): NumberSchema<Src>;
39
73
  hidden(isHidden?: boolean): NumberSchema<Src>;
74
+ /**
75
+ * Validate that the number is at most `max` (inclusive).
76
+ *
77
+ * @example
78
+ * const schema = s.number().max(4);
79
+ * export default c.define("/example.val.ts", schema, 3);
80
+ */
40
81
  max(max: number): NumberSchema<Src>;
82
+ /**
83
+ * Validate that the number is at least `min` (inclusive).
84
+ *
85
+ * @example
86
+ * const schema = s.number().min(1).max(4);
87
+ * export default c.define("/example.val.ts", schema, 3);
88
+ */
41
89
  min(min: number): NumberSchema<Src>;
42
90
  protected executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
43
91
  /**
@@ -46,12 +94,22 @@ export declare class NumberSchema<Src extends number | null> extends Schema<Src>
46
94
  * instead of a preview row that navigates to it.
47
95
  *
48
96
  * Static configuration, not a callback — see `render.ts`.
97
+ *
98
+ * @example
99
+ * const schema = s.array(s.number().render({ as: "inline" }));
100
+ * export default c.define("/example.val.ts", schema, [1, 2]);
49
101
  */
50
102
  render(input: FieldRender): NumberSchema<Src>;
51
103
  /**
52
104
  * How this VALUE is shown where a preview of it is needed — a row in a
53
105
  * sortable list, a reference dropdown, a search hit. Never how the field
54
106
  * itself is edited (that is `render`). See `preview.ts`.
107
+ *
108
+ * @example
109
+ * const schema = s.array(
110
+ * s.number().preview(({ val }) => ({ title: String(val) })),
111
+ * );
112
+ * export default c.define("/example.val.ts", schema, [1, 2]);
55
113
  */
56
114
  preview(select: ItemPreviewInput<Src>): NumberSchema<Src>;
57
115
  protected executePreviewItem(src: NonNullable<Src>): PreviewItem | null;
@@ -50,13 +50,65 @@ export declare class ObjectSchema<Props extends ObjectSchemaProps, Src extends O
50
50
  private readonly renderInput;
51
51
  private readonly previewInput;
52
52
  constructor(items: Props, 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
65
+ * .object({ street: s.string(), city: s.string() })
66
+ * .describe("Where the office is — shown on the contact page");
67
+ * export default c.define("/example.val.ts", schema, {
68
+ * street: "Torggata 1",
69
+ * city: "Oslo",
70
+ * });
71
+ */
53
72
  describe(description: string | null): ObjectSchema<Props, Src>;
73
+ /**
74
+ * Add a custom validation rule to this field.
75
+ *
76
+ * The function is called with the field's value and returns `false` when the
77
+ * value is fine, or a STRING with the message to show when it is not. Call it
78
+ * more than once to add more rules — they all run, and every message is
79
+ * reported.
80
+ *
81
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
82
+ * when the value is fine, and `true` is not one of the two answers.
83
+ *
84
+ * Validation runs in the Studio as you type, in `npx val validate` and
85
+ * before a publish.
86
+ *
87
+ * An object validator is the place for a rule that spans FIELDS — one field
88
+ * validated on its own belongs on that field's schema, where the error lands
89
+ * on the field itself.
90
+ *
91
+ * @example
92
+ * const schema = s
93
+ * .object({ from: s.number(), to: s.number() })
94
+ * .validate((val) =>
95
+ * val.from <= val.to ? false : "'from' must not be after 'to'",
96
+ * );
97
+ * export default c.define("/example.val.ts", schema, { from: 1, to: 4 });
98
+ */
54
99
  validate(validationFunction: (src: Src) => false | string): ObjectSchema<Props, Src>;
55
100
  protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
56
101
  protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
57
102
  nullable(): ObjectSchema<Props, Src | null>;
58
103
  readonly(isReadonly?: boolean): ObjectSchema<Props, Src>;
59
104
  hidden(isHidden?: boolean): ObjectSchema<Props, Src>;
105
+ /** The names of this object's `s.locale()` fields, in declaration order. */
106
+ protected localeFieldNames(): string[];
107
+ protected opensLocaleScope(): "field" | "key" | null;
108
+ protected localeScopeChildren(): {
109
+ key: string;
110
+ schema: Schema<SelectorSource>;
111
+ }[];
60
112
  protected executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
61
113
  /**
62
114
  * How this field is laid out in the editor when it is the item of an array
@@ -64,12 +116,30 @@ export declare class ObjectSchema<Props extends ObjectSchemaProps, Src extends O
64
116
  * instead of a preview row that navigates to it.
65
117
  *
66
118
  * Static configuration, not a callback — see `render.ts`.
119
+ *
120
+ * @example
121
+ * // A page builder: each row edits the object in place, instead of
122
+ * // navigating to it.
123
+ * const block = s
124
+ * .object({ heading: s.string(), body: s.string() })
125
+ * .render({ as: "inline" });
126
+ * export default c.define("/example.val.ts", s.array(block), [
127
+ * { heading: "Hello", body: "World" },
128
+ * ]);
67
129
  */
68
130
  render(input: FieldRender): ObjectSchema<Props, Src>;
69
131
  /**
70
132
  * How this VALUE is shown where a preview of it is needed — a row in a
71
133
  * sortable list, a reference dropdown, a search hit. Never how the field
72
134
  * itself is edited (that is `render`). See `preview.ts`.
135
+ *
136
+ * @example
137
+ * const author = s
138
+ * .object({ name: s.string(), role: s.string() })
139
+ * .preview(({ val }) => ({ title: val.name, subtitle: val.role }));
140
+ * export default c.define("/example.val.ts", s.array(author), [
141
+ * { name: "Ada", role: "Engineer" },
142
+ * ]);
73
143
  */
74
144
  preview(select: ItemPreviewInput<Src>): ObjectSchema<Props, Src>;
75
145
  protected executePreviewItem(src: NonNullable<Src>): PreviewItem | null;