@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
@@ -108,9 +108,81 @@ RO extends boolean = false> extends Schema<Src> {
108
108
  * sites.
109
109
  */
110
110
  externalLabel?: string | null);
111
+ /**
112
+ * Describe this field.
113
+ *
114
+ * The description is shown next to the field's label in the Val editor, so
115
+ * it is where you say what an editor needs to know but the field name cannot
116
+ * carry. It also travels in the serialized schema, which is what the AI
117
+ * assistant and the MCP tools read.
118
+ *
119
+ * Pass `null` to clear a description set earlier.
120
+ *
121
+ * @example
122
+ * const schema = s
123
+ * .record(s.string())
124
+ * .describe("Button labels, keyed by locale");
125
+ * export default c.define("/example.val.ts", schema, { en: "Read more" });
126
+ */
111
127
  describe(description: string | null): RecordSchema<T, K, Src>;
128
+ /**
129
+ * Add a custom validation rule to this field.
130
+ *
131
+ * The function is called with the field's value and returns `false` when the
132
+ * value is fine, or a STRING with the message to show when it is not. Call it
133
+ * more than once to add more rules — they all run, and every message is
134
+ * reported.
135
+ *
136
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
137
+ * when the value is fine, and `true` is not one of the two answers.
138
+ *
139
+ * Validation runs in the Studio as you type, in `npx val validate` and
140
+ * before a publish.
141
+ *
142
+ * The function is given the whole record. To validate one entry, put a
143
+ * `.validate(...)` on the ITEM schema instead — that reports the error on
144
+ * the entry's own row, and on a `.jsonValues()` record it does not force
145
+ * every entry to be loaded.
146
+ *
147
+ * @example
148
+ * const schema = s.record(s.string()).validate((val) =>
149
+ * "en" in val ? false : "There has to be an 'en' entry",
150
+ * );
151
+ * export default c.define("/example.val.ts", schema, { en: "Read more" });
152
+ */
112
153
  validate(validationFunction: (src: Src) => false | string): RecordSchema<T, K, Src>;
113
154
  protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
155
+ /**
156
+ * The check that a record with a DECLARED key set holds every one of them.
157
+ *
158
+ * `s.record(s.locale(), item)` has one entry per language and
159
+ * `s.record(s.union(s.literal("a"), s.literal("b")), item)` has both — the
160
+ * keys are part of the schema either way, so a missing one is a hole in the
161
+ * content rather than content nobody has written yet. TypeScript already
162
+ * demanded both keys of the second; this is the validator catching up, which
163
+ * is why the two now behave the same.
164
+ *
165
+ * Deferred to `resolveSchemaSourceFixes` even for the literal case, where the
166
+ * keys are known right here: a locale record's keys are in another file, and
167
+ * one code path for both is one message, one fix and one thing to be wrong.
168
+ */
169
+ private getDeclaredKeysValidation;
170
+ /**
171
+ * The key set this record's schema declares, computed once.
172
+ *
173
+ * `declaredKeySetOf` reads a SERIALIZED schema, and serializing the key
174
+ * schema walks it — twice per validate, and once per record instance, for an
175
+ * answer that is a property of the schema and cannot move between them. A
176
+ * schema is built once and validated against many times, so the walk happens
177
+ * on the first question and not on any after it.
178
+ */
179
+ private declaredKeys;
180
+ private declaredKeySet;
181
+ protected opensLocaleScope(): "field" | "key" | null;
182
+ protected localeScopeChildren(): {
183
+ key: string;
184
+ schema: Schema<SelectorSource>;
185
+ }[];
114
186
  private isRemoteUrl;
115
187
  /** Marks the validation errors reported at `path` as key errors (in place). */
116
188
  private markKeyErrorsAtPath;
@@ -128,10 +200,55 @@ RO extends boolean = false> extends Schema<Src> {
128
200
  * must come BEFORE `.external()`: afterwards the value is a `Schema`, and
129
201
  * there is no `.readonly()` left to call — which is the error the compiler
130
202
  * gives, and a clearer one than a runtime throw.
203
+ *
204
+ * @example
205
+ * const schema = s.record(s.string()).readonly();
206
+ * export default c.define("/example.val.ts", schema, { en: "Read more" });
131
207
  */
132
208
  readonly<B extends boolean = true>(isReadonly?: B): RecordSchema<T, K, Src, B>;
133
209
  hidden(isHidden?: boolean): RecordSchema<T, K, Src>;
210
+ /**
211
+ * Turn this record into a page router: its keys are route paths of your
212
+ * application, and they are validated against the router's conventions.
213
+ *
214
+ * `s.router(router, item)` builds the same thing in one call and is what
215
+ * most code should use — this is for adding a router to a record that
216
+ * already exists.
217
+ *
218
+ * @example
219
+ * import { nextAppRouter } from "../val.config";
220
+ * const schema = s
221
+ * .record(s.object({ title: s.string() }))
222
+ * .router(nextAppRouter);
223
+ * export default c.define("/app/[slug]/page.val.ts", schema, {
224
+ * "/a-page-slug": { title: "First Page" },
225
+ * });
226
+ */
134
227
  router(router: ValRouter): RecordSchema<T, K, Src>;
228
+ /**
229
+ * Allow the files in this gallery to be stored on Val's remote content host
230
+ * instead of in your repository.
231
+ *
232
+ * For `s.images()` and `s.files()`, which are records of media. Remote is off
233
+ * until this is called.
234
+ *
235
+ * Each entry is then keyed by its URL on the content host rather than by a
236
+ * path under `/public`. That key is not something to write by hand: upload
237
+ * in the Studio, or add a local path and let `npx val validate --fix`
238
+ * upload it and rewrite the key.
239
+ *
240
+ * @example
241
+ * const schema = s.images({ directory: "/public/val/images" }).remote();
242
+ * export default c.define("/content/images.val.ts", schema, {
243
+ * "https://remote.val.build/file/p/my-project/b/01/v/1.0.0/h/8f2a1c/f/3b9d70/p/public/val/images/hero.webp":
244
+ * {
245
+ * width: 1920,
246
+ * height: 1080,
247
+ * mimeType: "image/webp",
248
+ * alt: "Hero image",
249
+ * },
250
+ * });
251
+ */
135
252
  remote(): RecordSchema<T, K, Src>;
136
253
  /**
137
254
  * Store each entry's value in its own lazily-loaded `*.val.json` file instead
@@ -146,6 +263,20 @@ RO extends boolean = false> extends Schema<Src> {
146
263
  * nested inside an object/array/record is rejected at startup with a module
147
264
  * error, because the single-entry fetch endpoint, the Studio's content
148
265
  * substitution and content validation are all root-only.
266
+ *
267
+ * Must come BEFORE `.validate()` and `.preview()`: those are typed against
268
+ * the un-lazy source shape, so a validator or preview declared first cannot
269
+ * be carried over, and declaring one first throws rather than silently
270
+ * dropping it.
271
+ *
272
+ * @example
273
+ * import { nextAppRouter } from "../val.config";
274
+ * const schema = s
275
+ * .router(nextAppRouter, s.object({ title: s.string(), body: s.string() }))
276
+ * .jsonValues();
277
+ * export default c.define("/app/support/[slug]/page.val.ts", schema, {
278
+ * "/support/faq": c.json(() => import("./content/faq.val.json")),
279
+ * });
149
280
  */
150
281
  jsonValues(): RecordSchema<T, K, JsonValuesRecordSrc<T, K>>;
151
282
  /**
@@ -166,6 +297,19 @@ RO extends boolean = false> extends Schema<Src> {
166
297
  * nested external record would leave a hole in the middle of a module's
167
298
  * source, and the Studio, patching and validation all assume a module's
168
299
  * source is complete apart from its entries.
300
+ *
301
+ * @example
302
+ * const schema = s.record(s.object({ title: s.string() })).external("posts");
303
+ * export default c.define("/content/posts.val.ts", schema, c.external());
304
+ *
305
+ * @example
306
+ * // A read-only external record: `.readonly()` must come FIRST, because
307
+ * // afterwards there is no `.readonly()` left on the type to call.
308
+ * const schema = s
309
+ * .record(s.object({ title: s.string() }))
310
+ * .readonly()
311
+ * .external("posts");
312
+ * export default c.define("/content/posts.val.ts", schema, c.external());
169
313
  */
170
314
  external<L extends string>(label: L): RecordSchema<T, K, ExternalRecordSrc<SelectorOfSchema<T>, L, RO>, RO>;
171
315
  /**
@@ -197,6 +341,25 @@ RO extends boolean = false> extends Schema<Src> {
197
341
  * is the item of another container, in search, in references. What its
198
342
  * entries show is the ITEM schema's `preview`, not this. Never how the field
199
343
  * is edited (that is `render`). See `preview.ts`.
344
+ *
345
+ * @example
346
+ * // The preview of the RECORD, for where it is an item of something else.
347
+ * // What its own rows show is the ITEM schema's preview — see below.
348
+ * const labels = s.record(s.string()).preview(({ val }) => ({
349
+ * title: `${Object.keys(val).length} locales`,
350
+ * }));
351
+ * export default c.define("/example.val.ts", s.array(labels), [
352
+ * { en: "Read more" },
353
+ * ]);
354
+ *
355
+ * @example
356
+ * // Rows of a record come from the ITEM's preview, not the record's:
357
+ * const author = s
358
+ * .object({ name: s.string(), role: s.string() })
359
+ * .preview(({ val }) => ({ title: val.name, subtitle: val.role }));
360
+ * export default c.define("/example.val.ts", s.record(author), {
361
+ * ada: { name: "Ada", role: "Engineer" },
362
+ * });
200
363
  */
201
364
  preview(select: ItemPreviewInput<Src>): RecordSchema<T, K, Src>;
202
365
  /**
@@ -205,9 +368,32 @@ RO extends boolean = false> extends Schema<Src> {
205
368
  * instead of a preview row that navigates to it.
206
369
  *
207
370
  * Static configuration, not a callback — see `render.ts`.
371
+ *
372
+ * @example
373
+ * const schema = s.array(s.record(s.string()).render({ as: "inline" }));
374
+ * export default c.define("/example.val.ts", schema, [{ en: "Read more" }]);
208
375
  */
209
376
  render(input: FieldRender): RecordSchema<T, K, Src>;
210
377
  }
211
- export declare function record<K extends Schema<string>, S extends Schema<SelectorSource>>(key: K, schema: S): RecordSchema<S, K, Record<SelectorOfSchema<K>, SelectorOfSchema<S>>>;
378
+ /**
379
+ * The source of a record, given what its key schema says about its keys.
380
+ *
381
+ * A key schema that ENUMERATES its keys (a union of literals, or `s.locale()`)
382
+ * makes every one of them an entry of the record, and an entry nobody has
383
+ * written yet is `null` rather than absent — so the value type widens by
384
+ * `null`. That is the whole of option F at the type level: a half-translated
385
+ * record is a record with nulls in it, which is data you can count, filter and
386
+ * see in a diff, rather than keys that are simply not there.
387
+ *
388
+ * For a union of literals TypeScript already required every key, since
389
+ * `SelectorOfSchema<K>` is the literal union itself. `s.locale()` is a flavoured
390
+ * string, so it requires nothing here and the requirement is the validator's —
391
+ * deliberately: which languages exist is in the settings module, where the
392
+ * project can change it without a deploy.
393
+ */
394
+ export type RecordSrcOf<K extends Schema<string>, S extends Schema<SelectorSource>> = K extends {
395
+ __declaresRecordKeys: true;
396
+ } ? Record<SelectorOfSchema<K>, SelectorOfSchema<S> | null> : Record<SelectorOfSchema<K>, SelectorOfSchema<S>>;
397
+ export declare function record<K extends Schema<string>, S extends Schema<SelectorSource>>(key: K, schema: S): RecordSchema<S, K, RecordSrcOf<K, S>>;
212
398
  export declare function record<S extends Schema<SelectorSource>>(schema: S): RecordSchema<S, Schema<string>, Record<string, SelectorOfSchema<S>>>;
213
399
  export {};
@@ -31,9 +31,71 @@ export declare class RichTextSchema<O extends RichTextOptions, Src extends RichT
31
31
  private readonly renderInput;
32
32
  private readonly previewInput;
33
33
  constructor(options: O & ValidationOptions, opt?: boolean, customValidateFunctions?: CustomValidateFunction<Src>[], isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, renderInput?: FieldRender | null, previewInput?: ItemPreviewInput<Src> | null);
34
+ /**
35
+ * Describe this field.
36
+ *
37
+ * The description is shown next to the field's label in the Val editor, so
38
+ * it is where you say what an editor needs to know but the field name cannot
39
+ * carry. It also travels in the serialized schema, which is what the AI
40
+ * assistant and the MCP tools read.
41
+ *
42
+ * Pass `null` to clear a description set earlier.
43
+ *
44
+ * @example
45
+ * const schema = s.richtext().describe("The body of the article");
46
+ * export default c.define("/example.val.ts", schema, [
47
+ * { tag: "p", children: ["Hello"] },
48
+ * ]);
49
+ */
34
50
  describe(description: string | null): RichTextSchema<O, Src>;
51
+ /**
52
+ * Validate that the rich text holds at most `max` characters.
53
+ *
54
+ * Counted over the TEXT, not the markup: tags, attributes and the structure
55
+ * of the document do not add to it.
56
+ *
57
+ * @example
58
+ * const schema = s.richtext().maxLength(280);
59
+ * export default c.define("/example.val.ts", schema, [
60
+ * { tag: "p", children: ["Hello"] },
61
+ * ]);
62
+ */
35
63
  maxLength(max: number): RichTextSchema<O, Src>;
64
+ /**
65
+ * Validate that the rich text holds at least `min` characters.
66
+ *
67
+ * Counted over the TEXT, not the markup: tags, attributes and the structure
68
+ * of the document do not add to it.
69
+ *
70
+ * @example
71
+ * const schema = s.richtext().minLength(20).maxLength(280);
72
+ * export default c.define("/example.val.ts", schema, [
73
+ * { tag: "p", children: ["Long enough to clear the minimum length."] },
74
+ * ]);
75
+ */
36
76
  minLength(min: number): RichTextSchema<O, Src>;
77
+ /**
78
+ * Add a custom validation rule to this field.
79
+ *
80
+ * The function is called with the field's value and returns `false` when the
81
+ * value is fine, or a STRING with the message to show when it is not. Call it
82
+ * more than once to add more rules — they all run, and every message is
83
+ * reported.
84
+ *
85
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
86
+ * when the value is fine, and `true` is not one of the two answers.
87
+ *
88
+ * Validation runs in the Studio as you type, in `npx val validate` and
89
+ * before a publish.
90
+ *
91
+ * @example
92
+ * const schema = s.richtext().validate((val) =>
93
+ * val.length > 0 ? false : "Write something",
94
+ * );
95
+ * export default c.define("/example.val.ts", schema, [
96
+ * { tag: "p", children: ["Hello"] },
97
+ * ]);
98
+ */
37
99
  validate(validationFunction: (src: Src) => false | string): RichTextSchema<O, Src>;
38
100
  protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
39
101
  private internalValidate;
@@ -49,12 +111,28 @@ export declare class RichTextSchema<O extends RichTextOptions, Src extends RichT
49
111
  * instead of a preview row that navigates to it.
50
112
  *
51
113
  * Static configuration, not a callback — see `render.ts`.
114
+ *
115
+ * @example
116
+ * const schema = s.array(s.richtext().render({ as: "inline" }));
117
+ * export default c.define("/example.val.ts", schema, [
118
+ * [{ tag: "p", children: ["Hello"] }],
119
+ * ]);
52
120
  */
53
121
  render(input: FieldRender): RichTextSchema<O, Src>;
54
122
  /**
55
123
  * How this VALUE is shown where a preview of it is needed — a row in a
56
124
  * sortable list, a reference dropdown, a search hit. Never how the field
57
125
  * itself is edited (that is `render`). See `preview.ts`.
126
+ *
127
+ * @example
128
+ * const schema = s.array(
129
+ * s.richtext().preview(({ val }) => ({
130
+ * title: val.length > 0 ? "Rich text" : "Empty",
131
+ * })),
132
+ * );
133
+ * export default c.define("/example.val.ts", schema, [
134
+ * [{ tag: "p", children: ["Hello"] }],
135
+ * ]);
58
136
  */
59
137
  preview(select: ItemPreviewInput<Src>): RichTextSchema<O, Src>;
60
138
  protected executePreviewItem(src: NonNullable<Src>): PreviewItem | null;
@@ -40,6 +40,20 @@ export declare class RouteSchema<Src extends string | null> extends Schema<Src>
40
40
  private readonly renderInput;
41
41
  private readonly previewInput;
42
42
  constructor(options?: RouteOptions | undefined, opt?: boolean, customValidateFunctions?: ((src: Src) => false | string)[], isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, renderInput?: FieldRender | null, previewInput?: ItemPreviewInput<Src> | null);
43
+ /**
44
+ * Describe this field.
45
+ *
46
+ * The description is shown next to the field's label in the Val editor, so
47
+ * it is where you say what an editor needs to know but the field name cannot
48
+ * carry. It also travels in the serialized schema, which is what the AI
49
+ * assistant and the MCP tools read.
50
+ *
51
+ * Pass `null` to clear a description set earlier.
52
+ *
53
+ * @example
54
+ * const schema = s.route().describe("Where the button goes");
55
+ * export default c.define("/example.val.ts", schema, "/a-page-slug");
56
+ */
43
57
  describe(description: string | null): RouteSchema<Src>;
44
58
  /**
45
59
  * Specify a pattern for which routes are allowed.
@@ -67,6 +81,26 @@ export declare class RouteSchema<Src extends string | null> extends Schema<Src>
67
81
  * s.route().include(/^\/api\//).exclude(/^\/api\/internal\//) // API routes except internal
68
82
  */
69
83
  exclude(pattern: RegExp): RouteSchema<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
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
93
+ * when the value is fine, and `true` is not one of the two answers.
94
+ *
95
+ * Validation runs in the Studio as you type, in `npx val validate` and
96
+ * before a publish.
97
+ *
98
+ * @example
99
+ * const schema = s.route().validate((val) =>
100
+ * val.endsWith("/") ? "Leave off the trailing slash" : false,
101
+ * );
102
+ * export default c.define("/example.val.ts", schema, "/a-page-slug");
103
+ */
70
104
  validate(validationFunction: (src: Src) => false | string): RouteSchema<Src>;
71
105
  protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
72
106
  protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
@@ -80,12 +114,22 @@ export declare class RouteSchema<Src extends string | null> extends Schema<Src>
80
114
  * instead of a preview row that navigates to it.
81
115
  *
82
116
  * Static configuration, not a callback — see `render.ts`.
117
+ *
118
+ * @example
119
+ * const schema = s.array(s.route().render({ as: "inline" }));
120
+ * export default c.define("/example.val.ts", schema, ["/a-page-slug"]);
83
121
  */
84
122
  render(input: FieldRender): RouteSchema<Src>;
85
123
  /**
86
124
  * How this VALUE is shown where a preview of it is needed — a row in a
87
125
  * sortable list, a reference dropdown, a search hit. Never how the field
88
126
  * itself is edited (that is `render`). See `preview.ts`.
127
+ *
128
+ * @example
129
+ * const schema = s.array(
130
+ * s.route().preview(({ val }) => ({ title: val })),
131
+ * );
132
+ * export default c.define("/example.val.ts", schema, ["/a-page-slug"]);
89
133
  */
90
134
  preview(select: ItemPreviewInput<Src>): RouteSchema<Src>;
91
135
  protected executePreviewItem(src: NonNullable<Src>): PreviewItem | null;
@@ -38,6 +38,19 @@ export type SerializedSettingsSchema = {
38
38
  readonly?: boolean;
39
39
  hidden?: boolean;
40
40
  description?: string;
41
+ /**
42
+ * Which of Val's sections this is, where it is one.
43
+ *
44
+ * The name rather than the rule itself, because the rule is a closure and a
45
+ * closure does not survive JSON. Val owns both ends — every section's rules
46
+ * are in {@link sectionValidators} in this file — so the name is enough for
47
+ * {@link deserializeSchema} to put the same rules back, and a Studio that
48
+ * does not recognise the name simply runs none (see `sectionValidate`).
49
+ *
50
+ * Absent on the settings module itself, which is a bag of sections and has no
51
+ * rules of its own.
52
+ */
53
+ section?: string;
41
54
  };
42
55
  /**
43
56
  * An object whose keys are all optional: absent means unset.
@@ -58,7 +71,47 @@ export declare class SettingsSchema<Src extends {
58
71
  private readonly isReadonly;
59
72
  private readonly isHidden;
60
73
  private readonly description?;
61
- constructor(items: Record<string, Schema<SelectorSource>>, opt?: boolean, isReadonly?: boolean, isHidden?: boolean, description?: string | undefined);
74
+ /**
75
+ * Which section this is, where Val has rules about it beyond its keys.
76
+ *
77
+ * Not the `validate` the class deliberately does not offer: that one is the
78
+ * schema author's, and `s.settings()` takes no arguments to declare it
79
+ * with. This names a rule Val states about a shape Val owns — no language
80
+ * declared twice in `locales.available` is true of every project, and
81
+ * there is nowhere else it can be said. A single value's rules stay on its
82
+ * own schema; this is for the ones that need to see more than one value,
83
+ * or to report on a row of a list.
84
+ *
85
+ * A NAME rather than the function, so it survives serialization: the schema
86
+ * reaches the Studio's validation worker as JSON, and a closure would be
87
+ * dropped on the way — which is how these rules once went missing there.
88
+ */
89
+ private readonly section?;
90
+ constructor(items: Record<string, Schema<SelectorSource>>, opt?: boolean, isReadonly?: boolean, isHidden?: boolean, description?: string | undefined,
91
+ /**
92
+ * Which section this is, where Val has rules about it beyond its keys.
93
+ *
94
+ * Not the `validate` the class deliberately does not offer: that one is the
95
+ * schema author's, and `s.settings()` takes no arguments to declare it
96
+ * with. This names a rule Val states about a shape Val owns — no language
97
+ * declared twice in `locales.available` is true of every project, and
98
+ * there is nowhere else it can be said. A single value's rules stay on its
99
+ * own schema; this is for the ones that need to see more than one value,
100
+ * or to report on a row of a list.
101
+ *
102
+ * A NAME rather than the function, so it survives serialization: the schema
103
+ * reaches the Studio's validation worker as JSON, and a closure would be
104
+ * dropped on the way — which is how these rules once went missing there.
105
+ */
106
+ section?: string | undefined);
107
+ /**
108
+ * This section's rules, or `undefined` where there are none to run.
109
+ *
110
+ * A name this Val does not know means a schema written by a newer one. Its
111
+ * per-field validation still runs; only the section's own rule is missed,
112
+ * which is the mild half of the failure and better than refusing the schema.
113
+ */
114
+ private sectionValidate;
62
115
  protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
63
116
  protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
64
117
  nullable(): SettingsSchema<Src | null>;
@@ -66,24 +66,113 @@ export declare class StringSchema<Src extends string | null> extends Schema<Src>
66
66
  private readonly previewInput;
67
67
  private readonly isMultiline;
68
68
  constructor(options?: StringOptions | undefined, opt?: boolean, isRaw?: boolean, customValidateFunctions?: ((src: Src) => false | string)[], renderInput?: FieldRender | null, isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, previewInput?: ItemPreviewInput<Src> | null, isMultiline?: boolean);
69
+ /**
70
+ * Describe this field.
71
+ *
72
+ * The description is shown next to the field's label in the Val editor, so
73
+ * it is where you say what an editor needs to know but the field name cannot
74
+ * carry. It also travels in the serialized schema, which is what the AI
75
+ * assistant and the MCP tools read.
76
+ *
77
+ * Pass `null` to clear a description set earlier.
78
+ *
79
+ * @example
80
+ * const schema = s
81
+ * .string()
82
+ * .describe("Shown in the browser tab and in search results");
83
+ * export default c.define("/example.val.ts", schema, "Hello");
84
+ */
69
85
  describe(description: string | null): StringSchema<Src>;
70
86
  /**
71
87
  * @deprecated Use `minLength` instead
88
+ *
89
+ * @example
90
+ * const schema = s.string().minLength(2);
91
+ * export default c.define("/example.val.ts", schema, "Hi");
72
92
  */
73
93
  min(minLength: number): StringSchema<Src>;
94
+ /**
95
+ * Validate that the string is at least `minLength` characters long.
96
+ *
97
+ * @example
98
+ * const schema = s.string().minLength(2);
99
+ * export default c.define("/example.val.ts", schema, "Hi");
100
+ */
74
101
  minLength(minLength: number): StringSchema<Src>;
75
102
  /**
76
103
  * @deprecated Use `maxLength` instead
104
+ *
105
+ * @example
106
+ * const schema = s.string().maxLength(60);
107
+ * export default c.define("/example.val.ts", schema, "A title that fits");
77
108
  */
78
109
  max(maxLength: number): StringSchema<Src>;
110
+ /**
111
+ * Validate that the string is at most `maxLength` characters long.
112
+ *
113
+ * @example
114
+ * const schema = s.string().maxLength(60);
115
+ * export default c.define("/example.val.ts", schema, "A title that fits");
116
+ */
79
117
  maxLength(maxLength: number): StringSchema<Src>;
118
+ /**
119
+ * Validate that the string matches `regexp`.
120
+ *
121
+ * `message` is what the editor is shown when it does not match. Without one
122
+ * the error names the pattern, which is rarely what a non-developer needs.
123
+ *
124
+ * @example
125
+ * const schema = s
126
+ * .string()
127
+ * .regexp(/^[a-z0-9-]+$/, "Lower case letters, digits and dashes only");
128
+ * export default c.define("/example.val.ts", schema, "my-slug");
129
+ */
80
130
  regexp(regexp: RegExp, message?: string): StringSchema<Src>;
131
+ /**
132
+ * Add a custom validation rule to this field.
133
+ *
134
+ * The function is called with the field's value and returns `false` when the
135
+ * value is fine, or a STRING with the message to show when it is not. Call it
136
+ * more than once to add more rules — they all run, and every message is
137
+ * reported.
138
+ *
139
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
140
+ * when the value is fine, and `true` is not one of the two answers.
141
+ *
142
+ * Validation runs in the Studio as you type, in `npx val validate` and
143
+ * before a publish.
144
+ *
145
+ * @example
146
+ * const schema = s.string().validate((val) =>
147
+ * val.trim() === val ? false : "Must not start or end with a space",
148
+ * );
149
+ * export default c.define("/example.val.ts", schema, "Hello");
150
+ */
81
151
  validate(validationFunction: (src: Src) => false | string): StringSchema<Src>;
82
152
  protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
83
153
  protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
84
154
  nullable(): StringSchema<Src | null>;
85
155
  readonly(isReadonly?: boolean): StringSchema<Src>;
86
156
  hidden(isHidden?: boolean): StringSchema<Src>;
157
+ /**
158
+ * Do not stega encode this string.
159
+ *
160
+ * Val normally encodes an invisible pointer back to the source into every
161
+ * string it hands your app, which is what makes click-to-edit work. A raw
162
+ * string is handed over exactly as written — use it wherever the value is
163
+ * not rendered as visible text: a slug, a class name, an `id`, a URL,
164
+ * anything parsed or compared.
165
+ *
166
+ * @example
167
+ * const schema = s.object({
168
+ * title: s.string(),
169
+ * slug: s.string().raw(),
170
+ * });
171
+ * export default c.define("/example.val.ts", schema, {
172
+ * title: "Hello",
173
+ * slug: "hello",
174
+ * });
175
+ */
87
176
  raw(): StringSchema<Src extends null ? RawString | null : RawString>;
88
177
  protected executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
89
178
  protected executeSerialize(): SerializedSchema;
@@ -107,12 +196,22 @@ export declare class StringSchema<Src extends string | null> extends Schema<Src>
107
196
  *
108
197
  * Static configuration, not a callback — see `render.ts`. What a CONTAINER
109
198
  * shows for its items is a `preview`, which is a different thing entirely.
199
+ *
200
+ * @example
201
+ * const schema = s.array(s.string().render({ as: "inline" }));
202
+ * export default c.define("/example.val.ts", schema, ["First", "Second"]);
110
203
  */
111
204
  render(input: FieldRender): StringSchema<Src>;
112
205
  /**
113
206
  * How this VALUE is shown where a preview of it is needed — a row in a
114
207
  * sortable list, a reference dropdown, a search hit. Never how the field
115
208
  * itself is edited (that is `render`). See `preview.ts`.
209
+ *
210
+ * @example
211
+ * const schema = s.array(
212
+ * s.string().preview(({ val }) => ({ title: val })),
213
+ * );
214
+ * export default c.define("/example.val.ts", schema, ["First", "Second"]);
116
215
  */
117
216
  preview(select: ItemPreviewInput<Src>): StringSchema<Src>;
118
217
  protected executePreviewItem(src: NonNullable<Src>): PreviewItem | null;