@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
@@ -161,19 +161,217 @@ function _inherits(t, e) {
161
161
  // import { SerializedI18nSchema } from "./future/i18n";
162
162
  // import { SerializedOneOfSchema } from "./future/oneOf";
163
163
 
164
+ /**
165
+ * A locale scope's path, as something to read in a message.
166
+ *
167
+ * The walk names the item of an array or record `*`, which says nothing to
168
+ * whoever has to fix this. `[]` is the convention people already read as "each
169
+ * of these", and a path that is nothing BUT items has no field to name at all.
170
+ */
171
+ function describeScopePath(path) {
172
+ if (path.every(function (segment) {
173
+ return segment === "*";
174
+ })) {
175
+ return "its entries";
176
+ }
177
+ var rendered = "";
178
+ var _iterator = _createForOfIteratorHelper(path),
179
+ _step;
180
+ try {
181
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
182
+ var segment = _step.value;
183
+ if (segment === "*") {
184
+ rendered += "[]";
185
+ } else {
186
+ rendered += rendered === "" ? segment : ".".concat(segment);
187
+ }
188
+ }
189
+ } catch (err) {
190
+ _iterator.e(err);
191
+ } finally {
192
+ _iterator.f();
193
+ }
194
+ return "'".concat(rendered, "'");
195
+ }
164
196
  var Schema = /*#__PURE__*/function () {
165
197
  function Schema() {
166
198
  _classCallCheck(this, Schema);
199
+ /**
200
+ * The answer, computed once per schema.
201
+ *
202
+ * Memoised because the question is asked from `executeValidate`, which runs
203
+ * once per SOURCE instance: an array of five hundred blocks asks the item
204
+ * schema five hundred times, and each answer walks the schema below it
205
+ * looking for a nested scope. The answer cannot differ between two instances
206
+ * of the same schema — nothing here reads the source — so the walk is done
207
+ * once and the result handed back after that.
208
+ *
209
+ * The error is still reported at every instance's path. Val's validation
210
+ * errors are keyed by source path and that is where the Studio draws them, so
211
+ * a schema error on a row is a schema error on every row; reporting it on one
212
+ * arbitrary row would put it somewhere nobody was looking.
213
+ */
214
+ _defineProperty(this, "localeScopeErrorsMemo", void 0);
167
215
  }
168
216
  return _createClass(Schema, [{
217
+ key: "isLocaleField",
218
+ value:
219
+ /**
220
+ * Whether this node is a locale — the field that MARKS a scope, `s.locale()`.
221
+ *
222
+ * Only `LocaleSchema` overrides this. A base implementation rather than an
223
+ * abstract one on purpose: every other schema class answers no, and a
224
+ * question every class had to answer would be twenty edits for one yes.
225
+ *
226
+ * Not the same question as {@link opensLocaleScope}: a locale field marks the
227
+ * scope, the object AROUND it is the scope. See `localeScope.ts`.
228
+ */
229
+ function isLocaleField() {
230
+ return false;
231
+ }
232
+
233
+ /**
234
+ * Whether this node OPENS a locale scope, and what opens it.
235
+ *
236
+ * An object with a `s.locale()` field opens one; so does a record keyed by
237
+ * `s.locale()`. Everything below such a node is in that one language, which
238
+ * is why a scope may not contain another — see `localeScope.ts` for the rule
239
+ * and the reason it is validated rather than typed.
240
+ */
241
+ }, {
242
+ key: "opensLocaleScope",
243
+ value: function opensLocaleScope() {
244
+ return null;
245
+ }
246
+
247
+ /**
248
+ * The child schemas a locale scope reaches, by the path segment that reaches
249
+ * them.
250
+ *
251
+ * A structural walk that deliberately does NOT go through `executeSerialize`:
252
+ * serializing a subtree at every node to ask a question about its shape is
253
+ * quadratic, and this is asked during validation. `*` stands for the item of
254
+ * an array or record, which has no name of its own.
255
+ */
256
+ }, {
257
+ key: "localeScopeChildren",
258
+ value: function localeScopeChildren() {
259
+ return [];
260
+ }
261
+
262
+ /**
263
+ * Every locale scope opened strictly BELOW this node, by path.
264
+ *
265
+ * Stops descending at each one it finds, so a scope three deep is reported
266
+ * once, by the scope immediately enclosing it, rather than by every ancestor.
267
+ */
268
+ }, {
269
+ key: "localeScopesBelow",
270
+ value: function localeScopesBelow() {
271
+ var prefix = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
272
+ var found = [];
273
+ var _iterator2 = _createForOfIteratorHelper(this.localeScopeChildren()),
274
+ _step2;
275
+ try {
276
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
277
+ var _step2$value = _step2.value,
278
+ key = _step2$value.key,
279
+ schema = _step2$value.schema;
280
+ var _path = [].concat(_toConsumableArray(prefix), [key]);
281
+ var opened = schema.opensLocaleScope();
282
+ if (opened !== null) {
283
+ found.push({
284
+ path: _path,
285
+ kind: opened
286
+ });
287
+ continue;
288
+ }
289
+ found.push.apply(found, _toConsumableArray(schema.localeScopesBelow(_path)));
290
+ }
291
+ } catch (err) {
292
+ _iterator2.e(err);
293
+ } finally {
294
+ _iterator2.f();
295
+ }
296
+ return found;
297
+ }
298
+
299
+ /**
300
+ * The scope rule, as errors at this node — or `false` if it is not broken.
301
+ *
302
+ * Two things are wrong and both are wrong in the SCHEMA rather than in the
303
+ * content, so both are `schemaError`s: an object with two locale fields (the
304
+ * subtree below it would be in two languages at once), and a locale scope
305
+ * inside another (the inner one would silently override the outer for part
306
+ * of a subtree that is supposed to be one language throughout).
307
+ *
308
+ * Validated rather than typed on purpose. Expressing "no scope below this
309
+ * one" as a constraint means threading it through every schema class's type
310
+ * parameter, and the errors that fall out of a recursive constraint like
311
+ * that name the whole tree — an unrelated typo in a `.val.ts` would print
312
+ * pages. See the design notes on the locales PR.
313
+ */
314
+ }, {
315
+ key: "localeScopeErrors",
316
+ value: function localeScopeErrors() {
317
+ if (this.localeScopeErrorsMemo === undefined) {
318
+ this.localeScopeErrorsMemo = this.computeLocaleScopeErrors();
319
+ }
320
+ return this.localeScopeErrorsMemo;
321
+ }
322
+ }, {
323
+ key: "computeLocaleScopeErrors",
324
+ value: function computeLocaleScopeErrors() {
325
+ var errors = [];
326
+ var localeFields = this.localeFieldNames();
327
+ if (localeFields.length > 1) {
328
+ errors.push({
329
+ message: "An object can be in one language, so it can have one locale field. Found ".concat(localeFields.map(function (each) {
330
+ return "'".concat(each, "'");
331
+ }).join(", "), "."),
332
+ schemaError: true
333
+ });
334
+ }
335
+ var opened = this.opensLocaleScope();
336
+ if (opened !== null) {
337
+ var _iterator3 = _createForOfIteratorHelper(this.localeScopesBelow()),
338
+ _step3;
339
+ try {
340
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
341
+ var nested = _step3.value;
342
+ errors.push({
343
+ message: "Everything here is already in one language, so ".concat(describeScopePath(nested.path), " cannot set another. Move the ").concat(nested.kind === "key" ? "locale-keyed record" : "locale field", " out of this ").concat(opened === "key" ? "locale-keyed record" : "object", ", or take the outer one away."),
344
+ schemaError: true
345
+ });
346
+ }
347
+ } catch (err) {
348
+ _iterator3.e(err);
349
+ } finally {
350
+ _iterator3.f();
351
+ }
352
+ }
353
+ return errors;
354
+ }
355
+
356
+ /**
357
+ * This node's own `s.locale()` fields, where it is an object that has any.
358
+ *
359
+ * Empty everywhere else, so `localeScopeErrors` can live on the base class.
360
+ */
361
+ }, {
362
+ key: "localeFieldNames",
363
+ value: function localeFieldNames() {
364
+ return [];
365
+ }
366
+ }, {
169
367
  key: "executeCustomValidateFunctions",
170
368
  value: function executeCustomValidateFunctions(src, customValidateFunctions, ctx) {
171
369
  var errors = [];
172
- var _iterator = _createForOfIteratorHelper(customValidateFunctions),
173
- _step;
370
+ var _iterator4 = _createForOfIteratorHelper(customValidateFunctions),
371
+ _step4;
174
372
  try {
175
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
176
- var customValidateFunction = _step.value;
373
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
374
+ var customValidateFunction = _step4.value;
177
375
  try {
178
376
  var result = customValidateFunction(src, ctx);
179
377
  if (result) {
@@ -191,9 +389,9 @@ var Schema = /*#__PURE__*/function () {
191
389
  }
192
390
  }
193
391
  } catch (err) {
194
- _iterator.e(err);
392
+ _iterator4.e(err);
195
393
  } finally {
196
- _iterator.f();
394
+ _iterator4.f();
197
395
  }
198
396
  return errors;
199
397
  }
@@ -292,11 +490,11 @@ var Schema = /*#__PURE__*/function () {
292
490
  return incoming;
293
491
  }
294
492
  for (var pathS in incoming) {
295
- var _path = pathS;
296
- if (current[_path]) {
297
- current[_path] = current[_path].concat(incoming[_path]);
493
+ var _path2 = pathS;
494
+ if (current[_path2]) {
495
+ current[_path2] = current[_path2].concat(incoming[_path2]);
298
496
  } else {
299
- current[_path] = incoming[_path];
497
+ current[_path2] = incoming[_path2];
300
498
  }
301
499
  }
302
500
  return current;
@@ -331,6 +529,10 @@ var GetSource = Symbol["for"]("@valbuild/core/GetSource");
331
529
  var ValError = Symbol["for"]("@valbuild/core/ValError");
332
530
  var GenericSelector = /*#__PURE__*/_createClass(function GenericSelector(valOrExpr, path, schema, error) {
333
531
  _classCallCheck(this, GenericSelector);
532
+ _defineProperty(this, Path, void 0);
533
+ _defineProperty(this, GetSource, void 0);
534
+ _defineProperty(this, ValError, void 0);
535
+ _defineProperty(this, GetSchema, void 0);
334
536
  this[Path] = path;
335
537
  this[GetSource] = valOrExpr;
336
538
  this[ValError] = error;
@@ -462,6 +664,23 @@ var StringSchema = /*#__PURE__*/function (_Schema) {
462
664
  _this.isMultiline = isMultiline;
463
665
  return _this;
464
666
  }
667
+
668
+ /**
669
+ * Describe this field.
670
+ *
671
+ * The description is shown next to the field's label in the Val editor, so
672
+ * it is where you say what an editor needs to know but the field name cannot
673
+ * carry. It also travels in the serialized schema, which is what the AI
674
+ * assistant and the MCP tools read.
675
+ *
676
+ * Pass `null` to clear a description set earlier.
677
+ *
678
+ * @example
679
+ * const schema = s
680
+ * .string()
681
+ * .describe("Shown in the browser tab and in search results");
682
+ * export default c.define("/example.val.ts", schema, "Hello");
683
+ */
465
684
  _inherits(StringSchema, _Schema);
466
685
  return _createClass(StringSchema, [{
467
686
  key: "describe",
@@ -471,12 +690,24 @@ var StringSchema = /*#__PURE__*/function (_Schema) {
471
690
 
472
691
  /**
473
692
  * @deprecated Use `minLength` instead
693
+ *
694
+ * @example
695
+ * const schema = s.string().minLength(2);
696
+ * export default c.define("/example.val.ts", schema, "Hi");
474
697
  */
475
698
  }, {
476
699
  key: "min",
477
700
  value: function min(minLength) {
478
701
  return this.minLength(minLength);
479
702
  }
703
+
704
+ /**
705
+ * Validate that the string is at least `minLength` characters long.
706
+ *
707
+ * @example
708
+ * const schema = s.string().minLength(2);
709
+ * export default c.define("/example.val.ts", schema, "Hi");
710
+ */
480
711
  }, {
481
712
  key: "minLength",
482
713
  value: function minLength(_minLength) {
@@ -487,12 +718,24 @@ var StringSchema = /*#__PURE__*/function (_Schema) {
487
718
 
488
719
  /**
489
720
  * @deprecated Use `maxLength` instead
721
+ *
722
+ * @example
723
+ * const schema = s.string().maxLength(60);
724
+ * export default c.define("/example.val.ts", schema, "A title that fits");
490
725
  */
491
726
  }, {
492
727
  key: "max",
493
728
  value: function max(maxLength) {
494
729
  return this.maxLength(maxLength);
495
730
  }
731
+
732
+ /**
733
+ * Validate that the string is at most `maxLength` characters long.
734
+ *
735
+ * @example
736
+ * const schema = s.string().maxLength(60);
737
+ * export default c.define("/example.val.ts", schema, "A title that fits");
738
+ */
496
739
  }, {
497
740
  key: "maxLength",
498
741
  value: function maxLength(_maxLength) {
@@ -500,6 +743,19 @@ var StringSchema = /*#__PURE__*/function (_Schema) {
500
743
  maxLength: _maxLength
501
744
  }), this.opt, this.isRaw, this.customValidateFunctions, this.renderInput, this.isReadonly, this.isHidden, this.description, this.previewInput, this.isMultiline);
502
745
  }
746
+
747
+ /**
748
+ * Validate that the string matches `regexp`.
749
+ *
750
+ * `message` is what the editor is shown when it does not match. Without one
751
+ * the error names the pattern, which is rarely what a non-developer needs.
752
+ *
753
+ * @example
754
+ * const schema = s
755
+ * .string()
756
+ * .regexp(/^[a-z0-9-]+$/, "Lower case letters, digits and dashes only");
757
+ * export default c.define("/example.val.ts", schema, "my-slug");
758
+ */
503
759
  }, {
504
760
  key: "regexp",
505
761
  value: function regexp(_regexp, message) {
@@ -508,6 +764,27 @@ var StringSchema = /*#__PURE__*/function (_Schema) {
508
764
  regExpMessage: message
509
765
  }), this.opt, this.isRaw, this.customValidateFunctions, this.renderInput, this.isReadonly, this.isHidden, this.description, this.previewInput, this.isMultiline);
510
766
  }
767
+
768
+ /**
769
+ * Add a custom validation rule to this field.
770
+ *
771
+ * The function is called with the field's value and returns `false` when the
772
+ * value is fine, or a STRING with the message to show when it is not. Call it
773
+ * more than once to add more rules — they all run, and every message is
774
+ * reported.
775
+ *
776
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
777
+ * when the value is fine, and `true` is not one of the two answers.
778
+ *
779
+ * Validation runs in the Studio as you type, in `npx val validate` and
780
+ * before a publish.
781
+ *
782
+ * @example
783
+ * const schema = s.string().validate((val) =>
784
+ * val.trim() === val ? false : "Must not start or end with a space",
785
+ * );
786
+ * export default c.define("/example.val.ts", schema, "Hello");
787
+ */
511
788
  }, {
512
789
  key: "validate",
513
790
  value: function validate(validationFunction) {
@@ -598,6 +875,26 @@ var StringSchema = /*#__PURE__*/function (_Schema) {
598
875
  var isHidden = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
599
876
  return new StringSchema(this.options, this.opt, this.isRaw, this.customValidateFunctions, this.renderInput, this.isReadonly, isHidden, this.description, this.previewInput, this.isMultiline);
600
877
  }
878
+
879
+ /**
880
+ * Do not stega encode this string.
881
+ *
882
+ * Val normally encodes an invisible pointer back to the source into every
883
+ * string it hands your app, which is what makes click-to-edit work. A raw
884
+ * string is handed over exactly as written — use it wherever the value is
885
+ * not rendered as visible text: a slug, a class name, an `id`, a URL,
886
+ * anything parsed or compared.
887
+ *
888
+ * @example
889
+ * const schema = s.object({
890
+ * title: s.string(),
891
+ * slug: s.string().raw(),
892
+ * });
893
+ * export default c.define("/example.val.ts", schema, {
894
+ * title: "Hello",
895
+ * slug: "hello",
896
+ * });
897
+ */
601
898
  }, {
602
899
  key: "raw",
603
900
  value: function raw() {
@@ -663,6 +960,10 @@ var StringSchema = /*#__PURE__*/function (_Schema) {
663
960
  *
664
961
  * Static configuration, not a callback — see `render.ts`. What a CONTAINER
665
962
  * shows for its items is a `preview`, which is a different thing entirely.
963
+ *
964
+ * @example
965
+ * const schema = s.array(s.string().render({ as: "inline" }));
966
+ * export default c.define("/example.val.ts", schema, ["First", "Second"]);
666
967
  */
667
968
  }, {
668
969
  key: "render",
@@ -674,6 +975,12 @@ var StringSchema = /*#__PURE__*/function (_Schema) {
674
975
  * How this VALUE is shown where a preview of it is needed — a row in a
675
976
  * sortable list, a reference dropdown, a search hit. Never how the field
676
977
  * itself is edited (that is `render`). See `preview.ts`.
978
+ *
979
+ * @example
980
+ * const schema = s.array(
981
+ * s.string().preview(({ val }) => ({ title: val })),
982
+ * );
983
+ * export default c.define("/example.val.ts", schema, ["First", "Second"]);
677
984
  */
678
985
  }, {
679
986
  key: "preview",
@@ -734,12 +1041,59 @@ var ObjectSchema = /*#__PURE__*/function (_Schema) {
734
1041
  _this.previewInput = previewInput;
735
1042
  return _this;
736
1043
  }
1044
+
1045
+ /**
1046
+ * Describe this field.
1047
+ *
1048
+ * The description is shown next to the field's label in the Val editor, so
1049
+ * it is where you say what an editor needs to know but the field name cannot
1050
+ * carry. It also travels in the serialized schema, which is what the AI
1051
+ * assistant and the MCP tools read.
1052
+ *
1053
+ * Pass `null` to clear a description set earlier.
1054
+ *
1055
+ * @example
1056
+ * const schema = s
1057
+ * .object({ street: s.string(), city: s.string() })
1058
+ * .describe("Where the office is — shown on the contact page");
1059
+ * export default c.define("/example.val.ts", schema, {
1060
+ * street: "Torggata 1",
1061
+ * city: "Oslo",
1062
+ * });
1063
+ */
737
1064
  _inherits(ObjectSchema, _Schema);
738
1065
  return _createClass(ObjectSchema, [{
739
1066
  key: "describe",
740
1067
  value: function describe(description) {
741
1068
  return new ObjectSchema(this.items, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.renderInput, this.previewInput);
742
1069
  }
1070
+
1071
+ /**
1072
+ * Add a custom validation rule to this field.
1073
+ *
1074
+ * The function is called with the field's value and returns `false` when the
1075
+ * value is fine, or a STRING with the message to show when it is not. Call it
1076
+ * more than once to add more rules — they all run, and every message is
1077
+ * reported.
1078
+ *
1079
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
1080
+ * when the value is fine, and `true` is not one of the two answers.
1081
+ *
1082
+ * Validation runs in the Studio as you type, in `npx val validate` and
1083
+ * before a publish.
1084
+ *
1085
+ * An object validator is the place for a rule that spans FIELDS — one field
1086
+ * validated on its own belongs on that field's schema, where the error lands
1087
+ * on the field itself.
1088
+ *
1089
+ * @example
1090
+ * const schema = s
1091
+ * .object({ from: s.number(), to: s.number() })
1092
+ * .validate((val) =>
1093
+ * val.from <= val.to ? false : "'from' must not be after 'to'",
1094
+ * );
1095
+ * export default c.define("/example.val.ts", schema, { from: 1, to: 4 });
1096
+ */
743
1097
  }, {
744
1098
  key: "validate",
745
1099
  value: function validate(validationFunction) {
@@ -781,6 +1135,18 @@ var ObjectSchema = /*#__PURE__*/function (_Schema) {
781
1135
  } finally {
782
1136
  _iterator.f();
783
1137
  }
1138
+ var _iterator2 = _createForOfIteratorHelper(this.localeScopeErrors()),
1139
+ _step2;
1140
+ try {
1141
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
1142
+ var scopeError = _step2.value;
1143
+ error = this.appendValidationError(error, path, scopeError.message, src, scopeError.schemaError);
1144
+ }
1145
+ } catch (err) {
1146
+ _iterator2.e(err);
1147
+ } finally {
1148
+ _iterator2.f();
1149
+ }
784
1150
  for (var _i = 0, _Object$entries = Object.entries(this.items); _i < _Object$entries.length; _i++) {
785
1151
  var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
786
1152
  _key = _Object$entries$_i[0],
@@ -864,7 +1230,7 @@ var ObjectSchema = /*#__PURE__*/function (_Schema) {
864
1230
  key: "nullable",
865
1231
  value: function nullable() {
866
1232
  // Explicit type args: `previewInput` would otherwise pin inference to `Src`.
867
- return new ObjectSchema(this.items, true, [], this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
1233
+ return new ObjectSchema(this.items, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
868
1234
  }
869
1235
  }, {
870
1236
  key: "readonly",
@@ -878,6 +1244,35 @@ var ObjectSchema = /*#__PURE__*/function (_Schema) {
878
1244
  var isHidden = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
879
1245
  return new ObjectSchema(this.items, this.opt, this.customValidateFunctions, this.isReadonly, isHidden, this.description, this.renderInput, this.previewInput);
880
1246
  }
1247
+
1248
+ /** The names of this object's `s.locale()` fields, in declaration order. */
1249
+ }, {
1250
+ key: "localeFieldNames",
1251
+ value: function localeFieldNames() {
1252
+ var _this2 = this;
1253
+ return Object.keys(this.items).filter(function (key) {
1254
+ return _this2.items[key]["isLocaleField"]();
1255
+ });
1256
+ }
1257
+ }, {
1258
+ key: "opensLocaleScope",
1259
+ value: function opensLocaleScope() {
1260
+ // More than one is itself the error, reported by `localeScopeErrors`. It
1261
+ // still opens a scope: reporting "two locale fields" AND "a scope inside a
1262
+ // scope" for the same object would be two errors about one mistake.
1263
+ return this.localeFieldNames().length > 0 ? "field" : null;
1264
+ }
1265
+ }, {
1266
+ key: "localeScopeChildren",
1267
+ value: function localeScopeChildren() {
1268
+ var _this3 = this;
1269
+ return Object.keys(this.items).map(function (key) {
1270
+ return {
1271
+ key: key,
1272
+ schema: _this3.items[key]
1273
+ };
1274
+ });
1275
+ }
881
1276
  }, {
882
1277
  key: "executeCustomValidateAt",
883
1278
  value: function executeCustomValidateAt(path, src) {
@@ -892,6 +1287,16 @@ var ObjectSchema = /*#__PURE__*/function (_Schema) {
892
1287
  * instead of a preview row that navigates to it.
893
1288
  *
894
1289
  * Static configuration, not a callback — see `render.ts`.
1290
+ *
1291
+ * @example
1292
+ * // A page builder: each row edits the object in place, instead of
1293
+ * // navigating to it.
1294
+ * const block = s
1295
+ * .object({ heading: s.string(), body: s.string() })
1296
+ * .render({ as: "inline" });
1297
+ * export default c.define("/example.val.ts", s.array(block), [
1298
+ * { heading: "Hello", body: "World" },
1299
+ * ]);
895
1300
  */
896
1301
  }, {
897
1302
  key: "render",
@@ -903,6 +1308,14 @@ var ObjectSchema = /*#__PURE__*/function (_Schema) {
903
1308
  * How this VALUE is shown where a preview of it is needed — a row in a
904
1309
  * sortable list, a reference dropdown, a search hit. Never how the field
905
1310
  * itself is edited (that is `render`). See `preview.ts`.
1311
+ *
1312
+ * @example
1313
+ * const author = s
1314
+ * .object({ name: s.string(), role: s.string() })
1315
+ * .preview(({ val }) => ({ title: val.name, subtitle: val.role }));
1316
+ * export default c.define("/example.val.ts", s.array(author), [
1317
+ * { name: "Ada", role: "Engineer" },
1318
+ * ]);
906
1319
  */
907
1320
  }, {
908
1321
  key: "preview",
@@ -1005,12 +1418,54 @@ var ArraySchema = /*#__PURE__*/function (_Schema) {
1005
1418
  _this.renderInput = renderInput;
1006
1419
  return _this;
1007
1420
  }
1421
+
1422
+ /**
1423
+ * Describe this field.
1424
+ *
1425
+ * The description is shown next to the field's label in the Val editor, so
1426
+ * it is where you say what an editor needs to know but the field name cannot
1427
+ * carry. It also travels in the serialized schema, which is what the AI
1428
+ * assistant and the MCP tools read.
1429
+ *
1430
+ * Pass `null` to clear a description set earlier.
1431
+ *
1432
+ * @example
1433
+ * const schema = s
1434
+ * .array(s.string())
1435
+ * .describe("Bullet points shown under the heading");
1436
+ * export default c.define("/example.val.ts", schema, ["First", "Second"]);
1437
+ */
1008
1438
  _inherits(ArraySchema, _Schema);
1009
1439
  return _createClass(ArraySchema, [{
1010
1440
  key: "describe",
1011
1441
  value: function describe(description) {
1012
1442
  return new ArraySchema(this.item, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.previewInput, this.renderInput);
1013
1443
  }
1444
+
1445
+ /**
1446
+ * Add a custom validation rule to this field.
1447
+ *
1448
+ * The function is called with the field's value and returns `false` when the
1449
+ * value is fine, or a STRING with the message to show when it is not. Call it
1450
+ * more than once to add more rules — they all run, and every message is
1451
+ * reported.
1452
+ *
1453
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
1454
+ * when the value is fine, and `true` is not one of the two answers.
1455
+ *
1456
+ * Validation runs in the Studio as you type, in `npx val validate` and
1457
+ * before a publish.
1458
+ *
1459
+ * The function is given the whole array. To validate one item, put a
1460
+ * `.validate(...)` on the ITEM schema instead — that reports the error on
1461
+ * the item's own row.
1462
+ *
1463
+ * @example
1464
+ * const schema = s.array(s.string()).validate((val) =>
1465
+ * val.length <= 3 ? false : "At most 3 items",
1466
+ * );
1467
+ * export default c.define("/example.val.ts", schema, ["First", "Second"]);
1468
+ */
1014
1469
  }, {
1015
1470
  key: "validate",
1016
1471
  value: function validate(validationFunction) {
@@ -1082,7 +1537,7 @@ var ArraySchema = /*#__PURE__*/function (_Schema) {
1082
1537
  key: "nullable",
1083
1538
  value: function nullable() {
1084
1539
  // Explicit type args: `previewInput` would otherwise pin inference to `Src`.
1085
- return new ArraySchema(this.item, true, [], this.isReadonly, this.isHidden, this.description, this.previewInput, this.renderInput);
1540
+ return new ArraySchema(this.item, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.previewInput, this.renderInput);
1086
1541
  }
1087
1542
  }, {
1088
1543
  key: "readonly",
@@ -1096,6 +1551,14 @@ var ArraySchema = /*#__PURE__*/function (_Schema) {
1096
1551
  var isHidden = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
1097
1552
  return new ArraySchema(this.item, this.opt, this.customValidateFunctions, this.isReadonly, isHidden, this.description, this.previewInput, this.renderInput);
1098
1553
  }
1554
+ }, {
1555
+ key: "localeScopeChildren",
1556
+ value: function localeScopeChildren() {
1557
+ return [{
1558
+ key: "*",
1559
+ schema: this.item
1560
+ }];
1561
+ }
1099
1562
  }, {
1100
1563
  key: "executeCustomValidateAt",
1101
1564
  value: function executeCustomValidateAt(path, src) {
@@ -1218,6 +1681,25 @@ var ArraySchema = /*#__PURE__*/function (_Schema) {
1218
1681
  * is the item of another container, in search, in references. What its rows
1219
1682
  * show is the ITEM schema's `preview`, not this. Never how the field is
1220
1683
  * edited (that is `render`). See `preview.ts`.
1684
+ *
1685
+ * @example
1686
+ * // The preview of the ARRAY, for where it is an item of something else.
1687
+ * // What its own rows show is the ITEM schema's preview — see below.
1688
+ * const tags = s.array(s.string()).preview(({ val }) => ({
1689
+ * title: `${val.length} tags`,
1690
+ * }));
1691
+ * export default c.define("/example.val.ts", s.record(tags), {
1692
+ * "a-post": ["news", "release"],
1693
+ * });
1694
+ *
1695
+ * @example
1696
+ * // Rows of an array come from the ITEM's preview, not the array's:
1697
+ * const author = s
1698
+ * .object({ name: s.string(), role: s.string() })
1699
+ * .preview(({ val }) => ({ title: val.name, subtitle: val.role }));
1700
+ * export default c.define("/example.val.ts", s.array(author), [
1701
+ * { name: "Ada", role: "Engineer" },
1702
+ * ]);
1221
1703
  */
1222
1704
  }, {
1223
1705
  key: "preview",
@@ -1231,6 +1713,12 @@ var ArraySchema = /*#__PURE__*/function (_Schema) {
1231
1713
  * instead of a preview row that navigates to it.
1232
1714
  *
1233
1715
  * Static configuration, not a callback — see `render.ts`.
1716
+ *
1717
+ * @example
1718
+ * const schema = s.record(s.array(s.string()).render({ as: "inline" }));
1719
+ * export default c.define("/example.val.ts", schema, {
1720
+ * "a-post": ["news", "release"],
1721
+ * });
1234
1722
  */
1235
1723
  }, {
1236
1724
  key: "render",
@@ -1244,6 +1732,8 @@ var array = function array(schema) {
1244
1732
  };
1245
1733
 
1246
1734
  var LiteralSchema = /*#__PURE__*/function (_Schema) {
1735
+ /** Type-only marker: as a record key, this declares the key set. See `LocaleSchema`. */
1736
+
1247
1737
  function LiteralSchema(value) {
1248
1738
  var _this;
1249
1739
  var opt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
@@ -1265,12 +1755,50 @@ var LiteralSchema = /*#__PURE__*/function (_Schema) {
1265
1755
  _this.previewInput = previewInput;
1266
1756
  return _this;
1267
1757
  }
1758
+
1759
+ /**
1760
+ * Describe this field.
1761
+ *
1762
+ * The description is shown next to the field's label in the Val editor, so
1763
+ * it is where you say what an editor needs to know but the field name cannot
1764
+ * carry. It also travels in the serialized schema, which is what the AI
1765
+ * assistant and the MCP tools read.
1766
+ *
1767
+ * Pass `null` to clear a description set earlier.
1768
+ *
1769
+ * @example
1770
+ * const schema = s
1771
+ * .literal("hero")
1772
+ * .describe("The block type — it identifies the variant");
1773
+ * export default c.define("/example.val.ts", schema, "hero");
1774
+ */
1268
1775
  _inherits(LiteralSchema, _Schema);
1269
1776
  return _createClass(LiteralSchema, [{
1270
1777
  key: "describe",
1271
1778
  value: function describe(description) {
1272
1779
  return new LiteralSchema(this.value, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.renderInput, this.previewInput);
1273
1780
  }
1781
+
1782
+ /**
1783
+ * Add a custom validation rule to this field.
1784
+ *
1785
+ * The function is called with the field's value and returns `false` when the
1786
+ * value is fine, or a STRING with the message to show when it is not. Call it
1787
+ * more than once to add more rules — they all run, and every message is
1788
+ * reported.
1789
+ *
1790
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
1791
+ * when the value is fine, and `true` is not one of the two answers.
1792
+ *
1793
+ * Validation runs in the Studio as you type, in `npx val validate` and
1794
+ * before a publish.
1795
+ *
1796
+ * @example
1797
+ * const schema = s.literal("hero").validate((val) =>
1798
+ * val === "hero" ? false : "Unknown block type",
1799
+ * );
1800
+ * export default c.define("/example.val.ts", schema, "hero");
1801
+ */
1274
1802
  }, {
1275
1803
  key: "validate",
1276
1804
  value: function validate(validationFunction) {
@@ -1337,7 +1865,7 @@ var LiteralSchema = /*#__PURE__*/function (_Schema) {
1337
1865
  }, {
1338
1866
  key: "nullable",
1339
1867
  value: function nullable() {
1340
- return new LiteralSchema(this.value, true, [], this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
1868
+ return new LiteralSchema(this.value, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
1341
1869
  }
1342
1870
  }, {
1343
1871
  key: "readonly",
@@ -1365,6 +1893,10 @@ var LiteralSchema = /*#__PURE__*/function (_Schema) {
1365
1893
  * instead of a preview row that navigates to it.
1366
1894
  *
1367
1895
  * Static configuration, not a callback — see `render.ts`.
1896
+ *
1897
+ * @example
1898
+ * const schema = s.array(s.literal("hero").render({ as: "inline" }));
1899
+ * export default c.define("/example.val.ts", schema, ["hero"]);
1368
1900
  */
1369
1901
  }, {
1370
1902
  key: "render",
@@ -1376,6 +1908,12 @@ var LiteralSchema = /*#__PURE__*/function (_Schema) {
1376
1908
  * How this VALUE is shown where a preview of it is needed — a row in a
1377
1909
  * sortable list, a reference dropdown, a search hit. Never how the field
1378
1910
  * itself is edited (that is `render`). See `preview.ts`.
1911
+ *
1912
+ * @example
1913
+ * const schema = s.array(
1914
+ * s.literal("hero").preview(({ val }) => ({ title: val })),
1915
+ * );
1916
+ * export default c.define("/example.val.ts", schema, ["hero"]);
1379
1917
  */
1380
1918
  }, {
1381
1919
  key: "preview",
@@ -1424,8 +1962,30 @@ var literal = function literal(value) {
1424
1962
  return new LiteralSchema(value);
1425
1963
  };
1426
1964
 
1427
- var UnionSchema = /*#__PURE__*/function (_Schema) {
1428
- function UnionSchema(key, items) {
1965
+ /** The variant objects a discriminated union on `Key` may be built from. */
1966
+
1967
+ /**
1968
+ * At least one variant, so a union with none cannot be written.
1969
+ *
1970
+ * Everything downstream reads the first one where it needs any — `emptyOf`
1971
+ * takes `items[0]` for the value a new entry starts as — and a union with
1972
+ * nothing to select is not a thing an author means. `s.enum` says the same
1973
+ * about its values.
1974
+ */
1975
+
1976
+ /** The source type a discriminated union over `T` accepts. */
1977
+
1978
+ /**
1979
+ * One of several object shapes, told apart by a tag field.
1980
+ *
1981
+ * Every variant is an object with `key` set to a distinct `s.literal(...)`, and
1982
+ * the value of that field decides which variant a value is. Unlike
1983
+ * {@link EnumSchema} this is a CONTAINER: the variant's fields are the fields
1984
+ * being edited, and everything that walks the schema tree has to descend
1985
+ * through the matching variant.
1986
+ */
1987
+ var DiscriminatedUnionSchema = /*#__PURE__*/function (_Schema) {
1988
+ function DiscriminatedUnionSchema(key, items) {
1429
1989
  var _this;
1430
1990
  var opt = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
1431
1991
  var customValidateFunctions = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];
@@ -1434,8 +1994,8 @@ var UnionSchema = /*#__PURE__*/function (_Schema) {
1434
1994
  var description = arguments.length > 6 ? arguments[6] : undefined;
1435
1995
  var renderInput = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : null;
1436
1996
  var previewInput = arguments.length > 8 && arguments[8] !== undefined ? arguments[8] : null;
1437
- _classCallCheck(this, UnionSchema);
1438
- _this = _callSuper(this, UnionSchema);
1997
+ _classCallCheck(this, DiscriminatedUnionSchema);
1998
+ _this = _callSuper(this, DiscriminatedUnionSchema);
1439
1999
  _this.key = key;
1440
2000
  _this.items = items;
1441
2001
  _this.opt = opt;
@@ -1447,16 +2007,66 @@ var UnionSchema = /*#__PURE__*/function (_Schema) {
1447
2007
  _this.previewInput = previewInput;
1448
2008
  return _this;
1449
2009
  }
1450
- _inherits(UnionSchema, _Schema);
1451
- return _createClass(UnionSchema, [{
2010
+
2011
+ /**
2012
+ * Describe this field.
2013
+ *
2014
+ * The description is shown next to the field's label in the Val editor, so it is
2015
+ * where you say what an editor needs to know but the field name cannot carry. It
2016
+ * also travels in the serialized schema, which is what the AI assistant and the
2017
+ * MCP tools read.
2018
+ *
2019
+ * Pass `null` to clear a description set earlier.
2020
+ *
2021
+ * @example
2022
+ * const hero = s.object({ type: s.literal("hero"), title: s.string() });
2023
+ * const text = s.object({ type: s.literal("text"), body: s.string() });
2024
+ * const schema = s
2025
+ * .discriminatedUnion("type", hero, text)
2026
+ * .describe("What this section of the page is");
2027
+ * export default c.define("/example.val.ts", schema, {
2028
+ * type: "hero",
2029
+ * title: "Hello",
2030
+ * });
2031
+ */
2032
+ _inherits(DiscriminatedUnionSchema, _Schema);
2033
+ return _createClass(DiscriminatedUnionSchema, [{
1452
2034
  key: "describe",
1453
2035
  value: function describe(description) {
1454
- return new UnionSchema(this.key, this.items, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.renderInput, this.previewInput);
2036
+ return new DiscriminatedUnionSchema(this.key, this.items, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.renderInput, this.previewInput);
1455
2037
  }
2038
+
2039
+ /**
2040
+ * Add a custom validation rule to this field.
2041
+ *
2042
+ * The function is called with the field's value and returns `false` when the
2043
+ * value is fine, or a STRING with the message to show when it is not. Call it
2044
+ * more than once to add more rules — they all run, and every message is
2045
+ * reported.
2046
+ *
2047
+ * Validation runs in the Studio as you type, in `npx val validate` and before a
2048
+ * publish.
2049
+ *
2050
+ * The function is given the whole value, whichever variant it took. A rule
2051
+ * that only concerns one variant belongs on that variant's object schema.
2052
+ *
2053
+ * @example
2054
+ * const hero = s.object({ type: s.literal("hero"), title: s.string() });
2055
+ * const text = s.object({ type: s.literal("text"), body: s.string() });
2056
+ * const schema = s
2057
+ * .discriminatedUnion("type", hero, text)
2058
+ * .validate((val) =>
2059
+ * val.type === "hero" && !val.title ? "A hero needs a title" : false,
2060
+ * );
2061
+ * export default c.define("/example.val.ts", schema, {
2062
+ * type: "hero",
2063
+ * title: "Hello",
2064
+ * });
2065
+ */
1456
2066
  }, {
1457
2067
  key: "validate",
1458
2068
  value: function validate(validationFunction) {
1459
- return new UnionSchema(this.key, this.items, this.opt, this.customValidateFunctions.concat(validationFunction), this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
2069
+ return new DiscriminatedUnionSchema(this.key, this.items, this.opt, this.customValidateFunctions.concat(validationFunction), this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
1460
2070
  }
1461
2071
  }, {
1462
2072
  key: "executeValidate",
@@ -1464,142 +2074,135 @@ var UnionSchema = /*#__PURE__*/function (_Schema) {
1464
2074
  var customValidationErrors = this.executeCustomValidateFunctions(src, this.customValidateFunctions, {
1465
2075
  path: path
1466
2076
  });
2077
+ /**
2078
+ * A structural error at this path, unless the custom validators already
2079
+ * reported — in which case theirs are the answer and this one is dropped.
2080
+ * Faithful to what `s.union` did; changing it is a separate decision.
2081
+ */
2082
+ var structural = function structural(error) {
2083
+ return customValidationErrors.length > 0 ? _defineProperty({}, path, customValidationErrors) : _defineProperty({}, path, [error]);
2084
+ };
1467
2085
  var unknownSrc = src;
1468
2086
  if (this.opt && unknownSrc === null) {
1469
2087
  return customValidationErrors.length > 0 ? _defineProperty({}, path, customValidationErrors) : false;
1470
2088
  }
1471
2089
  if (!this.key) {
1472
- return customValidationErrors.length > 0 ? _defineProperty({}, path, customValidationErrors) : _defineProperty({}, path, [].concat(_toConsumableArray(customValidationErrors), [{
1473
- message: "Missing required first argument in union",
2090
+ return structural({
2091
+ message: "Missing required first argument in discriminated union",
1474
2092
  schemaError: true
1475
- }]));
2093
+ });
1476
2094
  }
1477
2095
  var key = this.key;
1478
2096
  if (!Array.isArray(this.items)) {
1479
- return customValidationErrors.length > 0 ? _defineProperty({}, path, customValidationErrors) : _defineProperty({}, path, [].concat(_toConsumableArray(customValidationErrors), [{
1480
- message: "A union schema must take more than 1 schema arguments",
2097
+ return structural({
2098
+ message: "A discriminated union schema must take more than 1 schema arguments",
1481
2099
  schemaError: true
1482
- }]));
2100
+ });
1483
2101
  }
1484
- if (typeof key === "string") {
1485
- // tagged union
1486
- if (this.items.some(function (item) {
1487
- return !(item instanceof ObjectSchema);
1488
- })) {
1489
- return customValidationErrors.length > 0 ? _defineProperty({}, path, customValidationErrors) : _defineProperty({}, path, [].concat(_toConsumableArray(customValidationErrors), [{
1490
- message: "Key is a string, so all schema items must be objects",
1491
- schemaError: true
1492
- }]));
1493
- }
1494
- var objectSchemas = this.items;
1495
- var serializedSchemas = objectSchemas.map(function (schema) {
1496
- return schema["executeSerialize"]();
2102
+ if (typeof key !== "string") {
2103
+ return structural({
2104
+ message: "The key of a discriminated union must be a string",
2105
+ schemaError: true
1497
2106
  });
1498
- var illegalSchemas = serializedSchemas.filter(function (schema) {
1499
- return !(schema.type === "object") || !(schema.items[key].type === "literal");
2107
+ }
2108
+ if (this.items.some(function (item) {
2109
+ return !(item instanceof ObjectSchema);
2110
+ })) {
2111
+ return structural({
2112
+ message: "All schema items of a discriminated union must be objects",
2113
+ schemaError: true
1500
2114
  });
1501
- if (illegalSchemas.length > 0) {
1502
- return customValidationErrors.length > 0 ? _defineProperty({}, path, customValidationErrors) : _defineProperty({}, path, [].concat(_toConsumableArray(customValidationErrors), [{
1503
- message: "All schema items must be objects with a key: ".concat(key, " that is a literal schema. Found: ").concat(JSON.stringify(illegalSchemas, null, 2)),
1504
- schemaError: true
1505
- }]));
1506
- }
1507
- var serializedObjectSchemas = serializedSchemas;
1508
- var optionalLiterals = serializedObjectSchemas.filter(function (schema) {
1509
- return schema.items[key].opt;
2115
+ }
2116
+ var objectSchemas = this.items;
2117
+ var serializedSchemas = objectSchemas.map(function (schema) {
2118
+ return schema["executeSerialize"]();
2119
+ });
2120
+ // `items[key]?` — a variant that OMITS the discriminator is exactly what
2121
+ // this check exists to report, so it must not throw while finding it.
2122
+ var illegalSchemas = serializedSchemas.filter(function (schema) {
2123
+ var _schema$items$key;
2124
+ return !(schema.type === "object") || ((_schema$items$key = schema.items[key]) === null || _schema$items$key === void 0 ? void 0 : _schema$items$key.type) !== "literal";
2125
+ });
2126
+ if (illegalSchemas.length > 0) {
2127
+ return structural({
2128
+ message: "All schema items must be objects with a key: ".concat(key, " that is a literal schema. Found: ").concat(JSON.stringify(illegalSchemas, null, 2)),
2129
+ schemaError: true
1510
2130
  });
1511
- if (optionalLiterals.length > 1) {
1512
- return customValidationErrors.length > 0 ? _defineProperty({}, path, customValidationErrors) : _defineProperty({}, path, [].concat(_toConsumableArray(customValidationErrors), [{
1513
- message: "Schema cannot have an optional keys: ".concat(key),
1514
- schemaError: true
1515
- }]));
1516
- }
1517
- if (_typeof(unknownSrc) !== "object") {
1518
- return customValidationErrors.length > 0 ? _defineProperty({}, path, customValidationErrors) : _defineProperty({}, path, [].concat(_toConsumableArray(customValidationErrors), [{
1519
- message: "Expected an object",
1520
- typeError: true
1521
- }]));
1522
- }
1523
- var objectSrc = unknownSrc;
1524
- if (objectSrc[key] === undefined) {
1525
- return customValidationErrors.length > 0 ? _defineProperty({}, path, customValidationErrors) : _defineProperty({}, path, [].concat(_toConsumableArray(customValidationErrors), [{
1526
- message: "Missing required key: ".concat(key),
1527
- typeError: true
1528
- }]));
1529
- }
1530
- var foundSchemaLiterals = [];
1531
- var _iterator = _createForOfIteratorHelper(serializedObjectSchemas),
1532
- _step;
1533
- try {
1534
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
1535
- var schema = _step.value;
1536
- var schemaKey = schema.items[key];
1537
- if (schemaKey.type === "literal") {
1538
- if (!foundSchemaLiterals.includes(schemaKey.value)) {
1539
- foundSchemaLiterals.push(schemaKey.value);
1540
- } else {
1541
- return customValidationErrors.length > 0 ? _defineProperty({}, path, customValidationErrors) : _defineProperty({}, path, [].concat(_toConsumableArray(customValidationErrors), [{
1542
- message: "Found duplicate key in schema: ".concat(schemaKey.value),
1543
- schemaError: true
1544
- }]));
1545
- }
1546
- }
1547
- }
1548
- } catch (err) {
1549
- _iterator.e(err);
1550
- } finally {
1551
- _iterator.f();
1552
- }
1553
- var objectSchemaAtKey = objectSchemas.find(function (schema) {
1554
- return !schema["items"][key]["executeValidate"](path, objectSrc[key]);
2131
+ }
2132
+ var serializedObjectSchemas = serializedSchemas;
2133
+ var optionalLiterals = serializedObjectSchemas.filter(function (schema) {
2134
+ return schema.items[key].opt;
2135
+ });
2136
+ if (optionalLiterals.length > 1) {
2137
+ return structural({
2138
+ message: "Schema cannot have an optional keys: ".concat(key),
2139
+ schemaError: true
1555
2140
  });
1556
- if (!objectSchemaAtKey) {
1557
- var keyPath = createValPathOfItem(path, key);
1558
- if (!keyPath) {
1559
- throw new Error("Internal error: could not create path at ".concat(!path && typeof path === "string" ? "<empty string>" : path, " at key ").concat(key));
1560
- }
1561
- return _defineProperty({}, keyPath, [{
1562
- message: "Invalid key: \"".concat(key, "\". Value was: \"").concat(objectSrc[key], "\". Valid values: ").concat(serializedObjectSchemas.map(function (schema) {
1563
- var keySchema = schema.items[key];
1564
- if (keySchema.type === "literal" && keySchema.value) {
1565
- return "\"".concat(keySchema.value, "\"");
1566
- } else {
1567
- // should not happen here, we already checked this
1568
- throw new Error("Expected literal schema, got ".concat(JSON.stringify(keySchema, null, 2)));
1569
- }
1570
- }).join(", "))
1571
- }]);
1572
- }
1573
- var error = objectSchemaAtKey["executeValidate"](path, objectSrc);
1574
- if (error) {
1575
- return error;
1576
- }
1577
- } else if (key instanceof LiteralSchema) {
1578
- if (this.items.some(function (item) {
1579
- return !(item instanceof LiteralSchema);
1580
- })) {
1581
- return _defineProperty({}, path, [{
1582
- message: "Key is a literal schema, so all schema items must be literals",
1583
- typeError: true
1584
- }]);
1585
- }
1586
- var literalItems = [key].concat(_toConsumableArray(this.items));
1587
- if (typeof unknownSrc === "string") {
1588
- var isMatch = literalItems.some(function (item) {
1589
- return !item["executeValidate"](path, unknownSrc);
1590
- });
1591
- if (!isMatch) {
1592
- return customValidationErrors.length > 0 ? _defineProperty({}, path, customValidationErrors) : _defineProperty({}, path, [].concat(_toConsumableArray(customValidationErrors), [{
1593
- message: "Union must match one of the following: ".concat(literalItems.map(function (item) {
1594
- return "\"".concat(item["value"], "\"");
1595
- }).join(", "))
1596
- }]));
2141
+ }
2142
+
2143
+ // `typeof null === "object"`, so null has to be named separately or it
2144
+ // reaches `objectSrc[key]` below and throws instead of reporting.
2145
+ if (unknownSrc === null || _typeof(unknownSrc) !== "object") {
2146
+ return structural({
2147
+ message: "Expected an object, got ".concat(unknownSrc === null ? "null" : _typeof(unknownSrc)),
2148
+ typeError: true
2149
+ });
2150
+ }
2151
+ var objectSrc = unknownSrc;
2152
+ if (objectSrc[key] === undefined) {
2153
+ return structural({
2154
+ message: "Missing required key: ".concat(key),
2155
+ typeError: true
2156
+ });
2157
+ }
2158
+ var foundSchemaLiterals = [];
2159
+ var _iterator = _createForOfIteratorHelper(serializedObjectSchemas),
2160
+ _step;
2161
+ try {
2162
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
2163
+ var schema = _step.value;
2164
+ var schemaKey = schema.items[key];
2165
+ if (schemaKey.type === "literal") {
2166
+ if (!foundSchemaLiterals.includes(schemaKey.value)) {
2167
+ foundSchemaLiterals.push(schemaKey.value);
2168
+ } else {
2169
+ return structural({
2170
+ message: "Found duplicate key in schema: ".concat(schemaKey.value),
2171
+ schemaError: true
2172
+ });
2173
+ }
1597
2174
  }
1598
2175
  }
1599
- } else {
1600
- return customValidationErrors.length > 0 ? _defineProperty({}, path, customValidationErrors) : _defineProperty({}, path, [].concat(_toConsumableArray(customValidationErrors), [{
1601
- message: "Expected a string or literal"
1602
- }]));
2176
+ } catch (err) {
2177
+ _iterator.e(err);
2178
+ } finally {
2179
+ _iterator.f();
2180
+ }
2181
+ var objectSchemaAtKey = objectSchemas.find(function (schema) {
2182
+ return !schema["items"][key]["executeValidate"](path, objectSrc[key]);
2183
+ });
2184
+ if (!objectSchemaAtKey) {
2185
+ var keyPath = createValPathOfItem(path, key);
2186
+ if (!keyPath) {
2187
+ throw new Error("Internal error: could not create path at ".concat(!path && typeof path === "string" ? "<empty string>" : path, " at key ").concat(key));
2188
+ }
2189
+ return _defineProperty({}, keyPath, [{
2190
+ message: "Invalid key: \"".concat(key, "\". Value was: \"").concat(objectSrc[key], "\". Valid values: ").concat(serializedObjectSchemas.map(function (schema) {
2191
+ var keySchema = schema.items[key];
2192
+ // The TYPE is the whole test: `s.literal("")` is a valid
2193
+ // discriminator, and a truthiness check threw on it here.
2194
+ if (keySchema.type === "literal") {
2195
+ return "\"".concat(keySchema.value, "\"");
2196
+ } else {
2197
+ // should not happen here, we already checked this
2198
+ throw new Error("Expected literal schema, got ".concat(JSON.stringify(keySchema, null, 2)));
2199
+ }
2200
+ }).join(", "))
2201
+ }]);
2202
+ }
2203
+ var error = objectSchemaAtKey["executeValidate"](path, objectSrc);
2204
+ if (error) {
2205
+ return error;
1603
2206
  }
1604
2207
  return customValidationErrors.length > 0 ? _defineProperty({}, path, customValidationErrors) : false;
1605
2208
  }
@@ -1621,11 +2224,11 @@ var UnionSchema = /*#__PURE__*/function (_Schema) {
1621
2224
  }])
1622
2225
  };
1623
2226
  }
1624
- if (!this.key) {
2227
+ if (typeof this.key !== "string" || !this.key) {
1625
2228
  return {
1626
2229
  success: false,
1627
2230
  errors: _defineProperty({}, path, [{
1628
- message: "Missing required first argument in union schema",
2231
+ message: "Missing required first argument in discriminated union schema",
1629
2232
  schemaError: true
1630
2233
  }])
1631
2234
  };
@@ -1634,135 +2237,82 @@ var UnionSchema = /*#__PURE__*/function (_Schema) {
1634
2237
  return {
1635
2238
  success: false,
1636
2239
  errors: _defineProperty({}, path, [{
1637
- message: "The schema of this value is wrong. Schema is neither a union of literals nor a tagged union (of objects)",
2240
+ message: "The schema of this value is wrong. A discriminated union must have an array of object schemas",
1638
2241
  schemaError: true
1639
2242
  }])
1640
2243
  };
1641
2244
  }
1642
- if (this.key instanceof LiteralSchema) {
1643
- var _ref23;
1644
- var success = false;
1645
- var errors = {};
1646
- var _iterator2 = _createForOfIteratorHelper((_ref23 = [this.key]).concat.apply(_ref23, _toConsumableArray(this.items))),
1647
- _step2;
1648
- try {
1649
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
1650
- var itemSchema = _step2.value;
1651
- if (!(itemSchema instanceof LiteralSchema)) {
1652
- return {
1653
- success: false,
1654
- errors: _defineProperty({}, path, [{
1655
- message: "Schema of value is a union of string, so all schema items must be literals",
1656
- schemaError: true
1657
- }])
1658
- };
1659
- }
1660
- if (typeof src !== "string") {
1661
- errors[path] = [{
1662
- message: "Expected 'string', got '".concat(_typeof(src), "'"),
1663
- typeError: true
1664
- }];
1665
- continue;
1666
- }
1667
- var res = itemSchema["executeAssert"](path, src);
1668
- if (res.success) {
1669
- success = true;
1670
- break;
1671
- } else {
1672
- for (var _i = 0, _Object$entries = Object.entries(res.errors); _i < _Object$entries.length; _i++) {
1673
- var _errors5;
1674
- var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
1675
- _key = _Object$entries$_i[0],
1676
- value = _Object$entries$_i[1];
1677
- if (!errors[_key]) {
1678
- errors[_key] = [];
1679
- }
1680
- (_errors5 = errors[_key]).push.apply(_errors5, _toConsumableArray(value));
1681
- }
1682
- }
1683
- }
1684
- } catch (err) {
1685
- _iterator2.e(err);
1686
- } finally {
1687
- _iterator2.f();
1688
- }
1689
- if (!success) {
1690
- return {
1691
- success: false,
1692
- errors: errors
1693
- };
1694
- }
1695
- return {
1696
- success: true,
1697
- data: src
1698
- };
1699
- } else if (typeof this.key === "string") {
1700
- var _success = false;
1701
- var _errors6 = {};
1702
- var _iterator3 = _createForOfIteratorHelper(this.items),
1703
- _step3;
1704
- try {
1705
- for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
1706
- var _itemSchema = _step3.value;
1707
- var _res = _itemSchema["executeAssert"](path, src);
1708
- if (_res.success) {
1709
- _success = true;
1710
- break;
1711
- } else {
1712
- for (var _i2 = 0, _Object$entries2 = Object.entries(_res.errors); _i2 < _Object$entries2.length; _i2++) {
1713
- var _errors7;
1714
- var _Object$entries2$_i = _slicedToArray(_Object$entries2[_i2], 2),
1715
- _key2 = _Object$entries2$_i[0],
1716
- _value = _Object$entries2$_i[1];
1717
- if (!_errors6[_key2]) {
1718
- _errors6[_key2] = [];
1719
- }
1720
- (_errors7 = _errors6[_key2]).push.apply(_errors7, _toConsumableArray(_value)); // by appending all type errors, we most likely get a lot of duplicate errors. Currently we believe this is correct though, but should probably be handled in when showing the errors to users
2245
+ var success = false;
2246
+ var errors = {};
2247
+ var _iterator2 = _createForOfIteratorHelper(this.items),
2248
+ _step2;
2249
+ try {
2250
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
2251
+ var itemSchema = _step2.value;
2252
+ var res = itemSchema["executeAssert"](path, src);
2253
+ if (res.success) {
2254
+ success = true;
2255
+ break;
2256
+ } else {
2257
+ for (var _i = 0, _Object$entries = Object.entries(res.errors); _i < _Object$entries.length; _i++) {
2258
+ var _errors4;
2259
+ var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
2260
+ _key = _Object$entries$_i[0],
2261
+ value = _Object$entries$_i[1];
2262
+ if (!errors[_key]) {
2263
+ errors[_key] = [];
1721
2264
  }
2265
+ (_errors4 = errors[_key]).push.apply(_errors4, _toConsumableArray(value)); // by appending all type errors, we most likely get a lot of duplicate errors. Currently we believe this is correct though, but should probably be handled in when showing the errors to users
1722
2266
  }
1723
2267
  }
1724
- } catch (err) {
1725
- _iterator3.e(err);
1726
- } finally {
1727
- _iterator3.f();
1728
- }
1729
- if (!_success) {
1730
- return {
1731
- success: false,
1732
- errors: _errors6
1733
- };
1734
2268
  }
1735
- return {
1736
- success: true,
1737
- data: src
1738
- };
1739
- } else {
2269
+ } catch (err) {
2270
+ _iterator2.e(err);
2271
+ } finally {
2272
+ _iterator2.f();
2273
+ }
2274
+ if (!success) {
1740
2275
  return {
1741
2276
  success: false,
1742
- errors: _defineProperty({}, path, [{
1743
- message: "The schema of this value is wrong. Schema is neither a union of literals nor a tagged union (of objects)",
1744
- schemaError: true
1745
- }])
2277
+ errors: errors
1746
2278
  };
1747
2279
  }
2280
+ return {
2281
+ success: true,
2282
+ data: src
2283
+ };
1748
2284
  }
1749
2285
  }, {
1750
2286
  key: "nullable",
1751
2287
  value: function nullable() {
1752
2288
  // Explicit type args: `previewInput` would otherwise pin inference to `Src`.
1753
- return new UnionSchema(this.key, this.items, true, [], this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
2289
+ return new DiscriminatedUnionSchema(this.key, this.items, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
1754
2290
  }
1755
2291
  }, {
1756
2292
  key: "readonly",
1757
2293
  value: function readonly() {
1758
2294
  var isReadonly = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
1759
- return new UnionSchema(this.key, this.items, this.opt, this.customValidateFunctions, isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
2295
+ return new DiscriminatedUnionSchema(this.key, this.items, this.opt, this.customValidateFunctions, isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
1760
2296
  }
1761
2297
  }, {
1762
2298
  key: "hidden",
1763
2299
  value: function hidden() {
1764
2300
  var isHidden = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
1765
- return new UnionSchema(this.key, this.items, this.opt, this.customValidateFunctions, this.isReadonly, isHidden, this.description, this.renderInput, this.previewInput);
2301
+ return new DiscriminatedUnionSchema(this.key, this.items, this.opt, this.customValidateFunctions, this.isReadonly, isHidden, this.description, this.renderInput, this.previewInput);
2302
+ }
2303
+ }, {
2304
+ key: "localeScopeChildren",
2305
+ value: function localeScopeChildren() {
2306
+ // Every variant is walked under the SAME path: whichever one the value
2307
+ // takes, that is where it sits. `s.enum()` has no children at all — its
2308
+ // values are strings, not schemas — which is why only this half of the old
2309
+ // `s.union` has this.
2310
+ return this.items.map(function (item) {
2311
+ return {
2312
+ key: "*",
2313
+ schema: item
2314
+ };
2315
+ });
1766
2316
  }
1767
2317
  }, {
1768
2318
  key: "executeCustomValidateAt",
@@ -1770,10 +2320,10 @@ var UnionSchema = /*#__PURE__*/function (_Schema) {
1770
2320
  var errors = this.executeCustomValidateFunctions(src, this.customValidateFunctions, {
1771
2321
  path: path
1772
2322
  });
1773
- // A tagged union's variants share the union's path, so `Internal.resolvePath`
1774
- // stops here and a variant's OWN validator would never be reached. Dispatch
1775
- // into the variant this value takes — the union is the only node that knows
1776
- // which one that is.
2323
+ // A discriminated union's variants share the union's path, so
2324
+ // `Internal.resolvePath` stops here and a variant's OWN validator would
2325
+ // never be reached. Dispatch into the variant this value takes — the union
2326
+ // is the only node that knows which one that is.
1777
2327
  var matched = this.matchedVariant(src);
1778
2328
  if (matched) {
1779
2329
  errors.push.apply(errors, _toConsumableArray(matched["executeCustomValidateAt"](path, src)));
@@ -1782,10 +2332,8 @@ var UnionSchema = /*#__PURE__*/function (_Schema) {
1782
2332
  }
1783
2333
 
1784
2334
  /**
1785
- * The variant schema a value takes, for a TAGGED union (string `key`). `null`
1786
- * for a literal union (its variants are leaves with nothing to descend into) or
1787
- * when the value matches none of them — a structural error `executeValidate`
1788
- * already reports.
2335
+ * The variant schema a value takes. `null` when the value matches none of
2336
+ * them — a structural error `executeValidate` already reports.
1789
2337
  */
1790
2338
  }, {
1791
2339
  key: "matchedVariant",
@@ -1802,11 +2350,11 @@ var UnionSchema = /*#__PURE__*/function (_Schema) {
1802
2350
  return null;
1803
2351
  }
1804
2352
  var objectSchemas = this.items;
1805
- var _iterator4 = _createForOfIteratorHelper(objectSchemas),
1806
- _step4;
2353
+ var _iterator3 = _createForOfIteratorHelper(objectSchemas),
2354
+ _step3;
1807
2355
  try {
1808
- for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
1809
- var item = _step4.value;
2356
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
2357
+ var item = _step3.value;
1810
2358
  if (!(item instanceof ObjectSchema)) {
1811
2359
  continue;
1812
2360
  }
@@ -1816,9 +2364,9 @@ var UnionSchema = /*#__PURE__*/function (_Schema) {
1816
2364
  }
1817
2365
  }
1818
2366
  } catch (err) {
1819
- _iterator4.e(err);
2367
+ _iterator3.e(err);
1820
2368
  } finally {
1821
- _iterator4.f();
2369
+ _iterator3.f();
1822
2370
  }
1823
2371
  return null;
1824
2372
  }
@@ -1829,11 +2377,21 @@ var UnionSchema = /*#__PURE__*/function (_Schema) {
1829
2377
  * instead of a preview row that navigates to it.
1830
2378
  *
1831
2379
  * Static configuration, not a callback — see `render.ts`.
2380
+ *
2381
+ * @example
2382
+ * // A page builder: the row draws the tag selector and the matched
2383
+ * // variant's fields.
2384
+ * const hero = s.object({ type: s.literal("hero"), title: s.string() });
2385
+ * const text = s.object({ type: s.literal("text"), body: s.string() });
2386
+ * const block = s.discriminatedUnion("type", hero, text).render({ as: "inline" });
2387
+ * export default c.define("/example.val.ts", s.array(block), [
2388
+ * { type: "hero", title: "Hello" },
2389
+ * ]);
1832
2390
  */
1833
2391
  }, {
1834
2392
  key: "render",
1835
2393
  value: function render(input) {
1836
- return new UnionSchema(this.key, this.items, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, input, this.previewInput);
2394
+ return new DiscriminatedUnionSchema(this.key, this.items, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, input, this.previewInput);
1837
2395
  }
1838
2396
 
1839
2397
  /**
@@ -1841,13 +2399,24 @@ var UnionSchema = /*#__PURE__*/function (_Schema) {
1841
2399
  * sortable list, a reference dropdown, a search hit. Never how the field
1842
2400
  * itself is edited (that is `render`). See `preview.ts`.
1843
2401
  *
1844
- * Without one of its own, a tagged union previews as the VARIANT the value
1845
- * takes — declare `preview` on the member objects and the union dispatches.
2402
+ * Without one of its own, a discriminated union previews as the VARIANT the
2403
+ * value takes — declare `preview` on the member objects and the union
2404
+ * dispatches.
2405
+ *
2406
+ * @example
2407
+ * const hero = s.object({ type: s.literal("hero"), title: s.string() });
2408
+ * const text = s.object({ type: s.literal("text"), body: s.string() });
2409
+ * const block = s
2410
+ * .discriminatedUnion("type", hero, text)
2411
+ * .preview(({ val }) => ({ title: val.type }));
2412
+ * export default c.define("/example.val.ts", s.array(block), [
2413
+ * { type: "hero", title: "Hello" },
2414
+ * ]);
1846
2415
  */
1847
2416
  }, {
1848
2417
  key: "preview",
1849
2418
  value: function preview(select) {
1850
- return new UnionSchema(this.key, this.items, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, select);
2419
+ return new DiscriminatedUnionSchema(this.key, this.items, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, select);
1851
2420
  }
1852
2421
  }, {
1853
2422
  key: "executePreviewItem",
@@ -1878,34 +2447,19 @@ var UnionSchema = /*#__PURE__*/function (_Schema) {
1878
2447
  }, {
1879
2448
  key: "executeSerialize",
1880
2449
  value: function executeSerialize() {
1881
- var _this$renderInput2, _this$customValidateF2;
1882
- if (typeof this.key === "string") {
1883
- var _this$renderInput, _this$customValidateF;
1884
- return {
1885
- type: "union",
1886
- render: (_this$renderInput = this.renderInput) !== null && _this$renderInput !== void 0 ? _this$renderInput : undefined,
1887
- preview: this.previewInput ? true : undefined,
1888
- key: this.key,
1889
- items: this.items.map(function (o) {
1890
- return o["executeSerialize"]();
1891
- }),
1892
- opt: this.opt,
1893
- customValidate: this.customValidateFunctions && ((_this$customValidateF = this.customValidateFunctions) === null || _this$customValidateF === void 0 ? void 0 : _this$customValidateF.length) > 0,
1894
- readonly: this.isReadonly,
1895
- hidden: this.isHidden,
1896
- description: this.description
1897
- };
1898
- }
2450
+ var _this$renderInput, _this$customValidateF;
1899
2451
  return {
1900
- type: "union",
1901
- render: (_this$renderInput2 = this.renderInput) !== null && _this$renderInput2 !== void 0 ? _this$renderInput2 : undefined,
2452
+ type: "discriminated-union",
2453
+ render: (_this$renderInput = this.renderInput) !== null && _this$renderInput !== void 0 ? _this$renderInput : undefined,
1902
2454
  preview: this.previewInput ? true : undefined,
1903
- key: this.key["executeSerialize"](),
2455
+ key: this.key,
2456
+ // Every item is an ObjectSchema — `executeValidate` reports a schema
2457
+ // error for anything else, but the type of `T` cannot say so.
1904
2458
  items: this.items.map(function (o) {
1905
2459
  return o["executeSerialize"]();
1906
2460
  }),
1907
2461
  opt: this.opt,
1908
- customValidate: this.customValidateFunctions && ((_this$customValidateF2 = this.customValidateFunctions) === null || _this$customValidateF2 === void 0 ? void 0 : _this$customValidateF2.length) > 0,
2462
+ customValidate: this.customValidateFunctions && ((_this$customValidateF = this.customValidateFunctions) === null || _this$customValidateF === void 0 ? void 0 : _this$customValidateF.length) > 0,
1909
2463
  readonly: this.isReadonly,
1910
2464
  hidden: this.isHidden,
1911
2465
  description: this.description
@@ -1918,48 +2472,46 @@ var UnionSchema = /*#__PURE__*/function (_Schema) {
1918
2472
  if (src === null) {
1919
2473
  return res;
1920
2474
  }
1921
- if (this.key instanceof LiteralSchema) {
1922
- return res;
1923
- }
1924
2475
  var unionKey = this.key;
1925
- if (typeof unionKey === "string") {
1926
- var thisSchema = this.items.find(
1927
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1928
- function (item) {
1929
- if (item instanceof ObjectSchema) {
1930
- var itemKey = item["items"][unionKey];
1931
- if (itemKey instanceof LiteralSchema) {
1932
- return _typeof(src) === "object" && unionKey in src && itemKey["value"] === src[unionKey];
1933
- }
1934
- }
1935
- return false;
1936
- });
1937
- if (thisSchema) {
1938
- var itemResult = thisSchema["executePreview"](sourcePath, src, scope);
1939
- for (var keyS in itemResult) {
1940
- var _key3 = keyS;
1941
- res[_key3] = itemResult[_key3];
1942
- }
1943
- return res;
1944
- }
2476
+ if (typeof unionKey !== "string") {
1945
2477
  res[sourcePath] = {
1946
2478
  status: "error",
1947
- message: "Could not find a matching (object) schema for the union key: ".concat(unionKey)
2479
+ message: "The schema of this value is wrong. Expected the key of a discriminated union to be a string. Got: '".concat(JSON.stringify(unionKey, null, 2), "'")
1948
2480
  };
2481
+ return res;
2482
+ }
2483
+ var thisSchema = this.items.find(
2484
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2485
+ function (item) {
2486
+ if (item instanceof ObjectSchema) {
2487
+ var itemKey = item["items"][unionKey];
2488
+ if (itemKey instanceof LiteralSchema) {
2489
+ return _typeof(src) === "object" && unionKey in src && itemKey["value"] === src[unionKey];
2490
+ }
2491
+ }
2492
+ return false;
2493
+ });
2494
+ if (thisSchema) {
2495
+ var itemResult = thisSchema["executePreview"](sourcePath, src, scope);
2496
+ for (var keyS in itemResult) {
2497
+ var _key2 = keyS;
2498
+ res[_key2] = itemResult[_key2];
2499
+ }
2500
+ return res;
1949
2501
  }
1950
2502
  res[sourcePath] = {
1951
2503
  status: "error",
1952
- message: "The schema of this value is wrong. Expected a object union schema, but union key is not a string. Got: '".concat(JSON.stringify(unionKey, null, 2), "'")
2504
+ message: "Could not find a matching (object) schema for the discriminated union key: ".concat(unionKey)
1953
2505
  };
1954
2506
  return res;
1955
2507
  }
1956
2508
  }]);
1957
2509
  }(Schema);
1958
- var union = function union(key) {
1959
- for (var _len = arguments.length, objects = new Array(_len > 1 ? _len - 1 : 0), _key4 = 1; _key4 < _len; _key4++) {
1960
- objects[_key4 - 1] = arguments[_key4];
2510
+ var discriminatedUnion = function discriminatedUnion(key) {
2511
+ for (var _len = arguments.length, objects = new Array(_len > 1 ? _len - 1 : 0), _key3 = 1; _key3 < _len; _key3++) {
2512
+ objects[_key3 - 1] = arguments[_key3];
1961
2513
  }
1962
- return new UnionSchema(key, objects);
2514
+ return new DiscriminatedUnionSchema(key, objects);
1963
2515
  };
1964
2516
 
1965
2517
  var RegEx$1 = /^(https?:\/\/[^/]+)\/file\/p\/([^/]+)\/b\/([^/]+)\/v\/([^/]+)\/h\/([^/]+)\/f\/([^/]+)\/p\/(.+)$/;
@@ -2930,17 +3482,94 @@ var ImageSchema = /*#__PURE__*/function (_Schema) {
2930
3482
  _this.previewInput = previewInput;
2931
3483
  return _this;
2932
3484
  }
3485
+
3486
+ /**
3487
+ * Describe this field.
3488
+ *
3489
+ * The description is shown next to the field's label in the Val editor, so
3490
+ * it is where you say what an editor needs to know but the field name cannot
3491
+ * carry. It also travels in the serialized schema, which is what the AI
3492
+ * assistant and the MCP tools read.
3493
+ *
3494
+ * Pass `null` to clear a description set earlier.
3495
+ *
3496
+ * @example
3497
+ * const schema = s
3498
+ * .image()
3499
+ * .describe("Shown at the top of the page, 16:9 works best");
3500
+ * export default c.define("/example.val.ts", schema, {
3501
+ * path: "/public/val/example.png",
3502
+ * width: 100,
3503
+ * height: 100,
3504
+ * mimeType: "image/png",
3505
+ * });
3506
+ */
2933
3507
  _inherits(ImageSchema, _Schema);
2934
3508
  return _createClass(ImageSchema, [{
2935
3509
  key: "describe",
2936
3510
  value: function describe(description) {
2937
3511
  return new ImageSchema(this.options, this.opt, this.isRemote, this.customValidateFunctions, this.moduleMetadata, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.renderInput, this.previewInput);
2938
3512
  }
3513
+
3514
+ /**
3515
+ * Store the image on Val's remote content host instead of in your repository.
3516
+ *
3517
+ * The bytes still go into the patch store when the image is uploaded — the
3518
+ * push to the remote host happens at publish. What changes is where the
3519
+ * published image lives: `path` becomes a remote URL rather than a path
3520
+ * under `/public`, so the repository does not grow with every upload.
3521
+ *
3522
+ * The `path` of a remote image is a URL on the content host, and it is not
3523
+ * something to write by hand: upload the image in the Studio, or write a
3524
+ * local path and let `npx val validate --fix` upload it and rewrite the
3525
+ * `path` to the ref below.
3526
+ *
3527
+ * @example
3528
+ * const schema = s
3529
+ * .image({ accept: "image/webp", directory: "/public/val/images" })
3530
+ * .remote();
3531
+ * export default c.define("/example.val.ts", schema, {
3532
+ * path: "https://remote.val.build/file/p/my-project/b/01/v/1.0.0/h/8f2a1c/f/3b9d70/p/public/val/images/example.webp",
3533
+ * width: 100,
3534
+ * height: 100,
3535
+ * mimeType: "image/webp",
3536
+ * });
3537
+ */
2939
3538
  }, {
2940
3539
  key: "remote",
2941
3540
  value: function remote() {
2942
3541
  return new ImageSchema(this.options, this.opt, true, this.customValidateFunctions, this.moduleMetadata, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
2943
3542
  }
3543
+
3544
+ /**
3545
+ * Add a custom validation rule to this field.
3546
+ *
3547
+ * The function is called with the field's value and returns `false` when the
3548
+ * value is fine, or a STRING with the message to show when it is not. Call it
3549
+ * more than once to add more rules — they all run, and every message is
3550
+ * reported.
3551
+ *
3552
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
3553
+ * when the value is fine, and `true` is not one of the two answers.
3554
+ *
3555
+ * Validation runs in the Studio as you type, in `npx val validate` and
3556
+ * before a publish.
3557
+ *
3558
+ * The second argument carries the `path` of the field being validated, for
3559
+ * when the message needs to say where the problem is.
3560
+ *
3561
+ * @example
3562
+ * const schema = s.image().validate((val) =>
3563
+ * val.alt ? false : "Every image needs alt text",
3564
+ * );
3565
+ * export default c.define("/example.val.ts", schema, {
3566
+ * path: "/public/val/example.png",
3567
+ * width: 100,
3568
+ * height: 100,
3569
+ * mimeType: "image/png",
3570
+ * alt: "An example",
3571
+ * });
3572
+ */
2944
3573
  }, {
2945
3574
  key: "validate",
2946
3575
  value: function validate(validationFunction) {
@@ -3158,6 +3787,17 @@ var ImageSchema = /*#__PURE__*/function (_Schema) {
3158
3787
  * instead of a preview row that navigates to it.
3159
3788
  *
3160
3789
  * Static configuration, not a callback — see `render.ts`.
3790
+ *
3791
+ * @example
3792
+ * const schema = s.array(s.image().render({ as: "inline" }));
3793
+ * export default c.define("/example.val.ts", schema, [
3794
+ * {
3795
+ * path: "/public/val/example.png",
3796
+ * width: 100,
3797
+ * height: 100,
3798
+ * mimeType: "image/png",
3799
+ * },
3800
+ * ]);
3161
3801
  */
3162
3802
  }, {
3163
3803
  key: "render",
@@ -3169,6 +3809,23 @@ var ImageSchema = /*#__PURE__*/function (_Schema) {
3169
3809
  * How this VALUE is shown where a preview of it is needed — a row in a
3170
3810
  * sortable list, a reference dropdown, a search hit. Never how the field
3171
3811
  * itself is edited (that is `render`). See `preview.ts`.
3812
+ *
3813
+ * @example
3814
+ * const schema = s.array(
3815
+ * s.image().preview(({ val }) => ({
3816
+ * title: val.alt ?? "Image",
3817
+ * image: val,
3818
+ * })),
3819
+ * );
3820
+ * export default c.define("/example.val.ts", schema, [
3821
+ * {
3822
+ * path: "/public/val/example.png",
3823
+ * width: 100,
3824
+ * height: 100,
3825
+ * mimeType: "image/png",
3826
+ * alt: "An example",
3827
+ * },
3828
+ * ]);
3172
3829
  */
3173
3830
  }, {
3174
3831
  key: "preview",
@@ -3265,6 +3922,21 @@ var RouteSchema = /*#__PURE__*/function (_Schema) {
3265
3922
  _this.previewInput = previewInput;
3266
3923
  return _this;
3267
3924
  }
3925
+
3926
+ /**
3927
+ * Describe this field.
3928
+ *
3929
+ * The description is shown next to the field's label in the Val editor, so
3930
+ * it is where you say what an editor needs to know but the field name cannot
3931
+ * carry. It also travels in the serialized schema, which is what the AI
3932
+ * assistant and the MCP tools read.
3933
+ *
3934
+ * Pass `null` to clear a description set earlier.
3935
+ *
3936
+ * @example
3937
+ * const schema = s.route().describe("Where the button goes");
3938
+ * export default c.define("/example.val.ts", schema, "/a-page-slug");
3939
+ */
3268
3940
  _inherits(RouteSchema, _Schema);
3269
3941
  return _createClass(RouteSchema, [{
3270
3942
  key: "describe",
@@ -3311,6 +3983,27 @@ var RouteSchema = /*#__PURE__*/function (_Schema) {
3311
3983
  exclude: pattern
3312
3984
  }), this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
3313
3985
  }
3986
+
3987
+ /**
3988
+ * Add a custom validation rule to this field.
3989
+ *
3990
+ * The function is called with the field's value and returns `false` when the
3991
+ * value is fine, or a STRING with the message to show when it is not. Call it
3992
+ * more than once to add more rules — they all run, and every message is
3993
+ * reported.
3994
+ *
3995
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
3996
+ * when the value is fine, and `true` is not one of the two answers.
3997
+ *
3998
+ * Validation runs in the Studio as you type, in `npx val validate` and
3999
+ * before a publish.
4000
+ *
4001
+ * @example
4002
+ * const schema = s.route().validate((val) =>
4003
+ * val.endsWith("/") ? "Leave off the trailing slash" : false,
4004
+ * );
4005
+ * export default c.define("/example.val.ts", schema, "/a-page-slug");
4006
+ */
3314
4007
  }, {
3315
4008
  key: "validate",
3316
4009
  value: function validate(validationFunction) {
@@ -3397,6 +4090,10 @@ var RouteSchema = /*#__PURE__*/function (_Schema) {
3397
4090
  * instead of a preview row that navigates to it.
3398
4091
  *
3399
4092
  * Static configuration, not a callback — see `render.ts`.
4093
+ *
4094
+ * @example
4095
+ * const schema = s.array(s.route().render({ as: "inline" }));
4096
+ * export default c.define("/example.val.ts", schema, ["/a-page-slug"]);
3400
4097
  */
3401
4098
  }, {
3402
4099
  key: "render",
@@ -3408,6 +4105,12 @@ var RouteSchema = /*#__PURE__*/function (_Schema) {
3408
4105
  * How this VALUE is shown where a preview of it is needed — a row in a
3409
4106
  * sortable list, a reference dropdown, a search hit. Never how the field
3410
4107
  * itself is edited (that is `render`). See `preview.ts`.
4108
+ *
4109
+ * @example
4110
+ * const schema = s.array(
4111
+ * s.route().preview(({ val }) => ({ title: val })),
4112
+ * );
4113
+ * export default c.define("/example.val.ts", schema, ["/a-page-slug"]);
3411
4114
  */
3412
4115
  }, {
3413
4116
  key: "preview",
@@ -3488,12 +4191,42 @@ var RichTextSchema = /*#__PURE__*/function (_Schema) {
3488
4191
  _this.previewInput = previewInput;
3489
4192
  return _this;
3490
4193
  }
4194
+
4195
+ /**
4196
+ * Describe this field.
4197
+ *
4198
+ * The description is shown next to the field's label in the Val editor, so
4199
+ * it is where you say what an editor needs to know but the field name cannot
4200
+ * carry. It also travels in the serialized schema, which is what the AI
4201
+ * assistant and the MCP tools read.
4202
+ *
4203
+ * Pass `null` to clear a description set earlier.
4204
+ *
4205
+ * @example
4206
+ * const schema = s.richtext().describe("The body of the article");
4207
+ * export default c.define("/example.val.ts", schema, [
4208
+ * { tag: "p", children: ["Hello"] },
4209
+ * ]);
4210
+ */
3491
4211
  _inherits(RichTextSchema, _Schema);
3492
4212
  return _createClass(RichTextSchema, [{
3493
4213
  key: "describe",
3494
4214
  value: function describe(description) {
3495
4215
  return new RichTextSchema(this.options, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.renderInput, this.previewInput);
3496
4216
  }
4217
+
4218
+ /**
4219
+ * Validate that the rich text holds at most `max` characters.
4220
+ *
4221
+ * Counted over the TEXT, not the markup: tags, attributes and the structure
4222
+ * of the document do not add to it.
4223
+ *
4224
+ * @example
4225
+ * const schema = s.richtext().maxLength(280);
4226
+ * export default c.define("/example.val.ts", schema, [
4227
+ * { tag: "p", children: ["Hello"] },
4228
+ * ]);
4229
+ */
3497
4230
  }, {
3498
4231
  key: "maxLength",
3499
4232
  value: function maxLength(max) {
@@ -3501,6 +4234,19 @@ var RichTextSchema = /*#__PURE__*/function (_Schema) {
3501
4234
  maxLength: max
3502
4235
  }), this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
3503
4236
  }
4237
+
4238
+ /**
4239
+ * Validate that the rich text holds at least `min` characters.
4240
+ *
4241
+ * Counted over the TEXT, not the markup: tags, attributes and the structure
4242
+ * of the document do not add to it.
4243
+ *
4244
+ * @example
4245
+ * const schema = s.richtext().minLength(20).maxLength(280);
4246
+ * export default c.define("/example.val.ts", schema, [
4247
+ * { tag: "p", children: ["Long enough to clear the minimum length."] },
4248
+ * ]);
4249
+ */
3504
4250
  }, {
3505
4251
  key: "minLength",
3506
4252
  value: function minLength(min) {
@@ -3508,6 +4254,29 @@ var RichTextSchema = /*#__PURE__*/function (_Schema) {
3508
4254
  minLength: min
3509
4255
  }), this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
3510
4256
  }
4257
+
4258
+ /**
4259
+ * Add a custom validation rule to this field.
4260
+ *
4261
+ * The function is called with the field's value and returns `false` when the
4262
+ * value is fine, or a STRING with the message to show when it is not. Call it
4263
+ * more than once to add more rules — they all run, and every message is
4264
+ * reported.
4265
+ *
4266
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
4267
+ * when the value is fine, and `true` is not one of the two answers.
4268
+ *
4269
+ * Validation runs in the Studio as you type, in `npx val validate` and
4270
+ * before a publish.
4271
+ *
4272
+ * @example
4273
+ * const schema = s.richtext().validate((val) =>
4274
+ * val.length > 0 ? false : "Write something",
4275
+ * );
4276
+ * export default c.define("/example.val.ts", schema, [
4277
+ * { tag: "p", children: ["Hello"] },
4278
+ * ]);
4279
+ */
3511
4280
  }, {
3512
4281
  key: "validate",
3513
4282
  value: function validate(validationFunction) {
@@ -3955,7 +4724,7 @@ var RichTextSchema = /*#__PURE__*/function (_Schema) {
3955
4724
  }, {
3956
4725
  key: "nullable",
3957
4726
  value: function nullable() {
3958
- return new RichTextSchema(this.options, true, [], this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
4727
+ return new RichTextSchema(this.options, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
3959
4728
  }
3960
4729
  }, {
3961
4730
  key: "readonly",
@@ -3983,6 +4752,12 @@ var RichTextSchema = /*#__PURE__*/function (_Schema) {
3983
4752
  * instead of a preview row that navigates to it.
3984
4753
  *
3985
4754
  * Static configuration, not a callback — see `render.ts`.
4755
+ *
4756
+ * @example
4757
+ * const schema = s.array(s.richtext().render({ as: "inline" }));
4758
+ * export default c.define("/example.val.ts", schema, [
4759
+ * [{ tag: "p", children: ["Hello"] }],
4760
+ * ]);
3986
4761
  */
3987
4762
  }, {
3988
4763
  key: "render",
@@ -3994,6 +4769,16 @@ var RichTextSchema = /*#__PURE__*/function (_Schema) {
3994
4769
  * How this VALUE is shown where a preview of it is needed — a row in a
3995
4770
  * sortable list, a reference dropdown, a search hit. Never how the field
3996
4771
  * itself is edited (that is `render`). See `preview.ts`.
4772
+ *
4773
+ * @example
4774
+ * const schema = s.array(
4775
+ * s.richtext().preview(({ val }) => ({
4776
+ * title: val.length > 0 ? "Rich text" : "Empty",
4777
+ * })),
4778
+ * );
4779
+ * export default c.define("/example.val.ts", schema, [
4780
+ * [{ tag: "p", children: ["Hello"] }],
4781
+ * ]);
3997
4782
  */
3998
4783
  }, {
3999
4784
  key: "preview",
@@ -4456,6 +5241,68 @@ function hasInlineExternalEntries(source) {
4456
5241
  return _typeof(source) === "object" && source !== null && !Array.isArray(source) && !isExternal(source) && Object.keys(source).length > 0;
4457
5242
  }
4458
5243
 
5244
+ /**
5245
+ * The keys a record is REQUIRED to hold, where its key schema says what they are.
5246
+ *
5247
+ * Most records have an open key set — `s.record(s.string(), item)` holds
5248
+ * whatever anyone adds — and this returns `null` for those. A record keyed by
5249
+ * something that enumerates its values is different in kind: the keys are part
5250
+ * of the schema, so a missing one is a hole in the content rather than content
5251
+ * nobody has written yet.
5252
+ *
5253
+ * Two key schemas enumerate. A union of literals does it locally, so its keys
5254
+ * are here. `s.locale()` does it in the settings module, which is another file
5255
+ * and so cannot be read from a serialized schema — that case says only which
5256
+ * kind it is, and is resolved against `locales.available` by
5257
+ * `resolveSchemaSourceFixes`, the same crossing `keyOf` and `route` make.
5258
+ */
5259
+
5260
+ /**
5261
+ * What `key` requires a record to hold, or `null` where it requires nothing.
5262
+ *
5263
+ * Works from the SERIALIZED schema so that the Studio, the validation worker
5264
+ * and the server all answer this the same way — none of them has the schema
5265
+ * instance, and `SchemaStore` deliberately holds serialized schemas only.
5266
+ */
5267
+ function declaredKeySetOf(key) {
5268
+ if (key === undefined) {
5269
+ return null;
5270
+ }
5271
+ if (key.type === "literal") {
5272
+ return {
5273
+ kind: "literals",
5274
+ keys: [key.value]
5275
+ };
5276
+ }
5277
+ if (key.type === "enum") {
5278
+ // An enum names every value it may take, so those values ARE the key set.
5279
+ // `discriminated-union` is deliberately absent: it discriminates objects
5280
+ // and cannot be a record key at all.
5281
+ return {
5282
+ kind: "literals",
5283
+ keys: _toConsumableArray(key.values)
5284
+ };
5285
+ }
5286
+ if (key.type === "locale") {
5287
+ return {
5288
+ kind: "locale"
5289
+ };
5290
+ }
5291
+ return null;
5292
+ }
5293
+
5294
+ /**
5295
+ * The declared keys a record is missing, in the order the schema declares them.
5296
+ *
5297
+ * Declaration order rather than the source's: this is read as "what is left to
5298
+ * write", and the answer should not reshuffle itself as entries are added.
5299
+ */
5300
+ function missingDeclaredKeys(declared, present) {
5301
+ return declared.filter(function (key) {
5302
+ return !present.includes(key);
5303
+ });
5304
+ }
5305
+
4459
5306
  /**
4460
5307
  * The source type of a `.jsonValues()` record: every entry value is EITHER a
4461
5308
  * lazily loaded {@link JsonSource} whose resolved content is the (loosened, see
@@ -4487,6 +5334,16 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
4487
5334
  var externalLabel = arguments.length > 12 && arguments[12] !== undefined ? arguments[12] : null;
4488
5335
  _classCallCheck(this, RecordSchema);
4489
5336
  _this = _callSuper(this, RecordSchema);
5337
+ /**
5338
+ * The key set this record's schema declares, computed once.
5339
+ *
5340
+ * `declaredKeySetOf` reads a SERIALIZED schema, and serializing the key
5341
+ * schema walks it — twice per validate, and once per record instance, for an
5342
+ * answer that is a property of the schema and cannot move between them. A
5343
+ * schema is built once and validated against many times, so the walk happens
5344
+ * on the first question and not on any after it.
5345
+ */
5346
+ _defineProperty(_this, "declaredKeys", void 0);
4490
5347
  _this.item = item;
4491
5348
  _this.opt = opt;
4492
5349
  _this.customValidateFunctions = customValidateFunctions;
@@ -4502,19 +5359,62 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
4502
5359
  _this.externalLabel = externalLabel;
4503
5360
  return _this;
4504
5361
  }
5362
+
5363
+ /**
5364
+ * Describe this field.
5365
+ *
5366
+ * The description is shown next to the field's label in the Val editor, so
5367
+ * it is where you say what an editor needs to know but the field name cannot
5368
+ * carry. It also travels in the serialized schema, which is what the AI
5369
+ * assistant and the MCP tools read.
5370
+ *
5371
+ * Pass `null` to clear a description set earlier.
5372
+ *
5373
+ * @example
5374
+ * const schema = s
5375
+ * .record(s.string())
5376
+ * .describe("Button labels, keyed by locale");
5377
+ * export default c.define("/example.val.ts", schema, { en: "Read more" });
5378
+ */
4505
5379
  _inherits(RecordSchema, _Schema);
4506
5380
  return _createClass(RecordSchema, [{
4507
5381
  key: "describe",
4508
5382
  value: function describe(description) {
4509
5383
  return new RecordSchema(this.item, this.opt, this.customValidateFunctions, this.currentRouter, this.keySchema, this.mediaOptions, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.isJsonValues, this.previewInput, this.renderInput, this.externalLabel);
4510
5384
  }
4511
- }, {
4512
- key: "validate",
4513
- value: function validate(validationFunction) {
4514
- return new RecordSchema(this.item, this.opt, [].concat(_toConsumableArray(this.customValidateFunctions), [validationFunction]), this.currentRouter, this.keySchema, this.mediaOptions, this.isReadonly, this.isHidden, this.description, this.isJsonValues, this.previewInput, this.renderInput, this.externalLabel);
4515
- }
4516
- }, {
4517
- key: "executeValidate",
5385
+
5386
+ /**
5387
+ * Add a custom validation rule to this field.
5388
+ *
5389
+ * The function is called with the field's value and returns `false` when the
5390
+ * value is fine, or a STRING with the message to show when it is not. Call it
5391
+ * more than once to add more rules — they all run, and every message is
5392
+ * reported.
5393
+ *
5394
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
5395
+ * when the value is fine, and `true` is not one of the two answers.
5396
+ *
5397
+ * Validation runs in the Studio as you type, in `npx val validate` and
5398
+ * before a publish.
5399
+ *
5400
+ * The function is given the whole record. To validate one entry, put a
5401
+ * `.validate(...)` on the ITEM schema instead — that reports the error on
5402
+ * the entry's own row, and on a `.jsonValues()` record it does not force
5403
+ * every entry to be loaded.
5404
+ *
5405
+ * @example
5406
+ * const schema = s.record(s.string()).validate((val) =>
5407
+ * "en" in val ? false : "There has to be an 'en' entry",
5408
+ * );
5409
+ * export default c.define("/example.val.ts", schema, { en: "Read more" });
5410
+ */
5411
+ }, {
5412
+ key: "validate",
5413
+ value: function validate(validationFunction) {
5414
+ return new RecordSchema(this.item, this.opt, [].concat(_toConsumableArray(this.customValidateFunctions), [validationFunction]), this.currentRouter, this.keySchema, this.mediaOptions, this.isReadonly, this.isHidden, this.description, this.isJsonValues, this.previewInput, this.renderInput, this.externalLabel);
5415
+ }
5416
+ }, {
5417
+ key: "executeValidate",
4518
5418
  value: function executeValidate(path, src) {
4519
5419
  var _this2 = this;
4520
5420
  var error = false;
@@ -4590,6 +5490,11 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
4590
5490
  error = _objectSpread2(_objectSpread2({}, error), {}, _defineProperty({}, path, [allFilesCheckError]));
4591
5491
  }
4592
5492
  }
5493
+ // A record whose keys are declared by its schema holds every one of them,
5494
+ // and an entry nobody has written yet is `null` rather than absent — so
5495
+ // `null` is a value here, not a value of the wrong type, and the item
5496
+ // schema is not asked about it.
5497
+ var hasDeclaredKeys = this.declaredKeySet() !== null;
4593
5498
  Object.entries(src).forEach(function (_ref5) {
4594
5499
  var _ref6 = _slicedToArray(_ref5, 2),
4595
5500
  key = _ref6[0],
@@ -4629,7 +5534,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
4629
5534
  _this2.markKeyErrorsAtPath(entryErr, subPath);
4630
5535
  }
4631
5536
  error = _this2.mergeValidationErrors(error, entryErr);
4632
- } else if (_this2.isJsonValues && isJson(elem)) ; else {
5537
+ } else if (hasDeclaredKeys && elem === null) ; else if (_this2.isJsonValues && isJson(elem)) ; else {
4633
5538
  // Falls through for a jsonValues record whose entry content is inlined
4634
5539
  // (loaded in the UI, or hand-authored): same as a plain record — validate
4635
5540
  // the value against the item schema.
@@ -4637,8 +5542,79 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
4637
5542
  error = _this2.mergeValidationErrors(error, subError);
4638
5543
  }
4639
5544
  });
5545
+ var declaredKeys = this.getDeclaredKeysValidation(path, src);
5546
+ error = this.mergeValidationErrors(error, declaredKeys);
5547
+ var _iterator2 = _createForOfIteratorHelper(this.localeScopeErrors()),
5548
+ _step2;
5549
+ try {
5550
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
5551
+ var scopeError = _step2.value;
5552
+ error = this.appendValidationError(error, path, scopeError.message, src, scopeError.schemaError);
5553
+ }
5554
+ } catch (err) {
5555
+ _iterator2.e(err);
5556
+ } finally {
5557
+ _iterator2.f();
5558
+ }
4640
5559
  return error;
4641
5560
  }
5561
+
5562
+ /**
5563
+ * The check that a record with a DECLARED key set holds every one of them.
5564
+ *
5565
+ * `s.record(s.locale(), item)` has one entry per language and
5566
+ * `s.record(s.union(s.literal("a"), s.literal("b")), item)` has both — the
5567
+ * keys are part of the schema either way, so a missing one is a hole in the
5568
+ * content rather than content nobody has written yet. TypeScript already
5569
+ * demanded both keys of the second; this is the validator catching up, which
5570
+ * is why the two now behave the same.
5571
+ *
5572
+ * Deferred to `resolveSchemaSourceFixes` even for the literal case, where the
5573
+ * keys are known right here: a locale record's keys are in another file, and
5574
+ * one code path for both is one message, one fix and one thing to be wrong.
5575
+ */
5576
+ }, {
5577
+ key: "getDeclaredKeysValidation",
5578
+ value: function getDeclaredKeysValidation(path, src) {
5579
+ if (this.keySchema === null) {
5580
+ return false;
5581
+ }
5582
+ var declared = this.declaredKeySet();
5583
+ if (declared === null) {
5584
+ return false;
5585
+ }
5586
+ return _defineProperty({}, path, [{
5587
+ fixes: ["record:fill-keys"],
5588
+ message: "Did not validate record keys. This error (record:fill-keys) should typically be processed by Val internally. Seeing this error most likely means you have a Val version mismatch.",
5589
+ value: {
5590
+ present: Object.keys(src),
5591
+ declared: declared.kind === "literals" ? declared.keys : null
5592
+ }
5593
+ }]);
5594
+ }
5595
+ }, {
5596
+ key: "declaredKeySet",
5597
+ value: function declaredKeySet() {
5598
+ if (this.declaredKeys === undefined) {
5599
+ this.declaredKeys = this.keySchema === null ? null : declaredKeySetOf(this.keySchema["executeSerialize"]());
5600
+ }
5601
+ return this.declaredKeys;
5602
+ }
5603
+ }, {
5604
+ key: "opensLocaleScope",
5605
+ value: function opensLocaleScope() {
5606
+ return this.keySchema !== null && this.keySchema["isLocaleField"]() ? "key" : null;
5607
+ }
5608
+ }, {
5609
+ key: "localeScopeChildren",
5610
+ value: function localeScopeChildren() {
5611
+ // The KEY schema is not a child here. A locale key is what opens this
5612
+ // record's scope; it is not something inside the scope.
5613
+ return [{
5614
+ key: "*",
5615
+ schema: this.item
5616
+ }];
5617
+ }
4642
5618
  }, {
4643
5619
  key: "isRemoteUrl",
4644
5620
  value: function isRemoteUrl(url) {
@@ -4869,6 +5845,10 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
4869
5845
  * must come BEFORE `.external()`: afterwards the value is a `Schema`, and
4870
5846
  * there is no `.readonly()` left to call — which is the error the compiler
4871
5847
  * gives, and a clearer one than a runtime throw.
5848
+ *
5849
+ * @example
5850
+ * const schema = s.record(s.string()).readonly();
5851
+ * export default c.define("/example.val.ts", schema, { en: "Read more" });
4872
5852
  */
4873
5853
  }, {
4874
5854
  key: "readonly",
@@ -4882,11 +5862,54 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
4882
5862
  var isHidden = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
4883
5863
  return new RecordSchema(this.item, this.opt, this.customValidateFunctions, this.currentRouter, this.keySchema, this.mediaOptions, this.isReadonly, isHidden, this.description, this.isJsonValues, this.previewInput, this.renderInput, this.externalLabel);
4884
5864
  }
5865
+
5866
+ /**
5867
+ * Turn this record into a page router: its keys are route paths of your
5868
+ * application, and they are validated against the router's conventions.
5869
+ *
5870
+ * `s.router(router, item)` builds the same thing in one call and is what
5871
+ * most code should use — this is for adding a router to a record that
5872
+ * already exists.
5873
+ *
5874
+ * @example
5875
+ * import { nextAppRouter } from "../val.config";
5876
+ * const schema = s
5877
+ * .record(s.object({ title: s.string() }))
5878
+ * .router(nextAppRouter);
5879
+ * export default c.define("/app/[slug]/page.val.ts", schema, {
5880
+ * "/a-page-slug": { title: "First Page" },
5881
+ * });
5882
+ */
4885
5883
  }, {
4886
5884
  key: "router",
4887
5885
  value: function router(_router) {
4888
5886
  return new RecordSchema(this.item, this.opt, this.customValidateFunctions, _router, this.keySchema, this.mediaOptions, this.isReadonly, this.isHidden, this.description, this.isJsonValues, this.previewInput, this.renderInput, this.externalLabel);
4889
5887
  }
5888
+
5889
+ /**
5890
+ * Allow the files in this gallery to be stored on Val's remote content host
5891
+ * instead of in your repository.
5892
+ *
5893
+ * For `s.images()` and `s.files()`, which are records of media. Remote is off
5894
+ * until this is called.
5895
+ *
5896
+ * Each entry is then keyed by its URL on the content host rather than by a
5897
+ * path under `/public`. That key is not something to write by hand: upload
5898
+ * in the Studio, or add a local path and let `npx val validate --fix`
5899
+ * upload it and rewrite the key.
5900
+ *
5901
+ * @example
5902
+ * const schema = s.images({ directory: "/public/val/images" }).remote();
5903
+ * export default c.define("/content/images.val.ts", schema, {
5904
+ * "https://remote.val.build/file/p/my-project/b/01/v/1.0.0/h/8f2a1c/f/3b9d70/p/public/val/images/hero.webp":
5905
+ * {
5906
+ * width: 1920,
5907
+ * height: 1080,
5908
+ * mimeType: "image/webp",
5909
+ * alt: "Hero image",
5910
+ * },
5911
+ * });
5912
+ */
4890
5913
  }, {
4891
5914
  key: "remote",
4892
5915
  value: function remote() {
@@ -4908,6 +5931,20 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
4908
5931
  * nested inside an object/array/record is rejected at startup with a module
4909
5932
  * error, because the single-entry fetch endpoint, the Studio's content
4910
5933
  * substitution and content validation are all root-only.
5934
+ *
5935
+ * Must come BEFORE `.validate()` and `.preview()`: those are typed against
5936
+ * the un-lazy source shape, so a validator or preview declared first cannot
5937
+ * be carried over, and declaring one first throws rather than silently
5938
+ * dropping it.
5939
+ *
5940
+ * @example
5941
+ * import { nextAppRouter } from "../val.config";
5942
+ * const schema = s
5943
+ * .router(nextAppRouter, s.object({ title: s.string(), body: s.string() }))
5944
+ * .jsonValues();
5945
+ * export default c.define("/app/support/[slug]/page.val.ts", schema, {
5946
+ * "/support/faq": c.json(() => import("./content/faq.val.json")),
5947
+ * });
4911
5948
  */
4912
5949
  }, {
4913
5950
  key: "jsonValues",
@@ -4955,6 +5992,19 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
4955
5992
  * nested external record would leave a hole in the middle of a module's
4956
5993
  * source, and the Studio, patching and validation all assume a module's
4957
5994
  * source is complete apart from its entries.
5995
+ *
5996
+ * @example
5997
+ * const schema = s.record(s.object({ title: s.string() })).external("posts");
5998
+ * export default c.define("/content/posts.val.ts", schema, c.external());
5999
+ *
6000
+ * @example
6001
+ * // A read-only external record: `.readonly()` must come FIRST, because
6002
+ * // afterwards there is no `.readonly()` left on the type to call.
6003
+ * const schema = s
6004
+ * .record(s.object({ title: s.string() }))
6005
+ * .readonly()
6006
+ * .external("posts");
6007
+ * export default c.define("/content/posts.val.ts", schema, c.external());
4958
6008
  */
4959
6009
  }, {
4960
6010
  key: "external",
@@ -5214,6 +6264,25 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5214
6264
  * is the item of another container, in search, in references. What its
5215
6265
  * entries show is the ITEM schema's `preview`, not this. Never how the field
5216
6266
  * is edited (that is `render`). See `preview.ts`.
6267
+ *
6268
+ * @example
6269
+ * // The preview of the RECORD, for where it is an item of something else.
6270
+ * // What its own rows show is the ITEM schema's preview — see below.
6271
+ * const labels = s.record(s.string()).preview(({ val }) => ({
6272
+ * title: `${Object.keys(val).length} locales`,
6273
+ * }));
6274
+ * export default c.define("/example.val.ts", s.array(labels), [
6275
+ * { en: "Read more" },
6276
+ * ]);
6277
+ *
6278
+ * @example
6279
+ * // Rows of a record come from the ITEM's preview, not the record's:
6280
+ * const author = s
6281
+ * .object({ name: s.string(), role: s.string() })
6282
+ * .preview(({ val }) => ({ title: val.name, subtitle: val.role }));
6283
+ * export default c.define("/example.val.ts", s.record(author), {
6284
+ * ada: { name: "Ada", role: "Engineer" },
6285
+ * });
5217
6286
  */
5218
6287
  }, {
5219
6288
  key: "preview",
@@ -5227,6 +6296,10 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5227
6296
  * instead of a preview row that navigates to it.
5228
6297
  *
5229
6298
  * Static configuration, not a callback — see `render.ts`.
6299
+ *
6300
+ * @example
6301
+ * const schema = s.array(s.record(s.string()).render({ as: "inline" }));
6302
+ * export default c.define("/example.val.ts", schema, [{ en: "Read more" }]);
5230
6303
  */
5231
6304
  }, {
5232
6305
  key: "render",
@@ -5236,6 +6309,23 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5236
6309
  }]);
5237
6310
  }(Schema);
5238
6311
 
6312
+ /**
6313
+ * The source of a record, given what its key schema says about its keys.
6314
+ *
6315
+ * A key schema that ENUMERATES its keys (a union of literals, or `s.locale()`)
6316
+ * makes every one of them an entry of the record, and an entry nobody has
6317
+ * written yet is `null` rather than absent — so the value type widens by
6318
+ * `null`. That is the whole of option F at the type level: a half-translated
6319
+ * record is a record with nulls in it, which is data you can count, filter and
6320
+ * see in a diff, rather than keys that are simply not there.
6321
+ *
6322
+ * For a union of literals TypeScript already required every key, since
6323
+ * `SelectorOfSchema<K>` is the literal union itself. `s.locale()` is a flavoured
6324
+ * string, so it requires nothing here and the requirement is the validator's —
6325
+ * deliberately: which languages exist is in the settings module, where the
6326
+ * project can change it without a deploy.
6327
+ */
6328
+
5239
6329
  // Overload: with key schema
5240
6330
 
5241
6331
  // Overload: without key schema
@@ -5274,6 +6364,31 @@ function record(keyOrSchema, schema) {
5274
6364
  * written without removing the key.
5275
6365
  */
5276
6366
 
6367
+ /**
6368
+ * The languages this project publishes.
6369
+ *
6370
+ * Content rather than configuration, and deliberately: which languages a site
6371
+ * has is a decision the people who write it make, and under a build-time
6372
+ * constant it took a developer and a deploy. It is the same move `assistant`
6373
+ * makes with `enabled`.
6374
+ *
6375
+ * Val ships no list of its own. A project with no `locales` section has not
6376
+ * said it is translated, and nothing about locales appears anywhere — no picker
6377
+ * in the Studio, no checks, nothing.
6378
+ *
6379
+ * **This list is a decision with a blast radius.** Every locale in content is
6380
+ * checked against it, so removing one invalidates the content that uses it, and
6381
+ * adding one leaves every locale-keyed record short of a language until it is
6382
+ * filled in. That is the intended behaviour — a language that is declared and
6383
+ * missing everywhere is worth being told about — but it is why the Studio warns
6384
+ * before saving a removal rather than treating this as an ordinary field.
6385
+ *
6386
+ * There is no default language, deliberately. Every locale-specific field asks
6387
+ * which language it is in, and a default is exactly the answer that lets that
6388
+ * question go unanswered — content ends up filed under a language nobody chose,
6389
+ * which is the state translation is meant to make visible.
6390
+ */
6391
+
5277
6392
  /**
5278
6393
  * The assistant: whether editors have one, and what it is told about the
5279
6394
  * project on every message.
@@ -5334,8 +6449,32 @@ settings) {
5334
6449
  return "unconfigured";
5335
6450
  }
5336
6451
 
5337
- var BooleanSchema = /*#__PURE__*/function (_Schema) {
5338
- function BooleanSchema() {
6452
+ /**
6453
+ * One of the languages the project publishes.
6454
+ *
6455
+ * The set is NOT here. It is declared in the settings module — `locales.available`
6456
+ * — so that adding a language is something the people who write the content can
6457
+ * do, and so that one list drives validation, the Studio's picker and the
6458
+ * translation notes. This schema knows only that the value is one of them, which
6459
+ * is checked cross-module through the `locale:check-locale` fix, exactly as
6460
+ * `keyOf` and `route` check theirs.
6461
+ *
6462
+ * Never stega encoded (see `stegaEncode.ts`): a locale goes into `<html lang>`,
6463
+ * into `hreflang` and into `Intl` constructors, none of which survive invisible
6464
+ * characters.
6465
+ */
6466
+ var LocaleSchema = /*#__PURE__*/function (_Schema) {
6467
+ /**
6468
+ * Type-only marker: as a record key, this schema DECLARES the key set.
6469
+ *
6470
+ * `declare` so it exists in the type and nowhere at runtime — reading it off
6471
+ * an instance gives `undefined`, and nothing should. It is how `record()`
6472
+ * knows to type entries as `Item | null`: a language nobody has translated
6473
+ * into is a null entry, not an absent key. See `DeclaredKeySet` and
6474
+ * `RecordSrcOf`.
6475
+ */
6476
+
6477
+ function LocaleSchema() {
5339
6478
  var _this;
5340
6479
  var opt = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
5341
6480
  var customValidateFunctions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
@@ -5344,8 +6483,8 @@ var BooleanSchema = /*#__PURE__*/function (_Schema) {
5344
6483
  var description = arguments.length > 4 ? arguments[4] : undefined;
5345
6484
  var renderInput = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : null;
5346
6485
  var previewInput = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : null;
5347
- _classCallCheck(this, BooleanSchema);
5348
- _this = _callSuper(this, BooleanSchema);
6486
+ _classCallCheck(this, LocaleSchema);
6487
+ _this = _callSuper(this, LocaleSchema);
5349
6488
  _this.opt = opt;
5350
6489
  _this.customValidateFunctions = customValidateFunctions;
5351
6490
  _this.isReadonly = isReadonly;
@@ -5355,30 +6494,80 @@ var BooleanSchema = /*#__PURE__*/function (_Schema) {
5355
6494
  _this.previewInput = previewInput;
5356
6495
  return _this;
5357
6496
  }
5358
- _inherits(BooleanSchema, _Schema);
5359
- return _createClass(BooleanSchema, [{
6497
+
6498
+ /**
6499
+ * A note to whoever edits this field, shown under it in the Studio.
6500
+ *
6501
+ * Pass `null` to clear a description set earlier.
6502
+ *
6503
+ * @example
6504
+ * const schema = s.object({
6505
+ * locale: s.locale().describe("The language everything below is written in"),
6506
+ * title: s.string(),
6507
+ * });
6508
+ * export default c.define("/example.val.ts", schema, {
6509
+ * locale: "nb-NO",
6510
+ * title: "Vinterjakke",
6511
+ * });
6512
+ */
6513
+ _inherits(LocaleSchema, _Schema);
6514
+ return _createClass(LocaleSchema, [{
5360
6515
  key: "describe",
5361
6516
  value: function describe(description) {
5362
- return new BooleanSchema(this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.renderInput, this.previewInput);
6517
+ return new LocaleSchema(this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.renderInput, this.previewInput);
5363
6518
  }
6519
+
6520
+ /**
6521
+ * A check of your own, on top of "is this one of the project's languages".
6522
+ *
6523
+ * Return a message when the value is WRONG and `false` when it is fine — the
6524
+ * two answers are a complaint and no complaint, so write the check as a
6525
+ * ternary rather than as `ok || "message"`, which returns `true` and `true`
6526
+ * is neither.
6527
+ *
6528
+ * @example
6529
+ * const schema = s.object({
6530
+ * locale: s.locale().validate((val) =>
6531
+ * val === "en-US" ? "This section is not translated into English yet" : false,
6532
+ * ),
6533
+ * title: s.string(),
6534
+ * });
6535
+ * export default c.define("/example.val.ts", schema, {
6536
+ * locale: "nb-NO",
6537
+ * title: "Vinterjakke",
6538
+ * });
6539
+ */
5364
6540
  }, {
5365
6541
  key: "validate",
5366
6542
  value: function validate(validationFunction) {
5367
- return new BooleanSchema(this.opt, [].concat(_toConsumableArray(this.customValidateFunctions), [validationFunction]), this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
6543
+ return new LocaleSchema(this.opt, this.customValidateFunctions.concat(validationFunction), this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
5368
6544
  }
5369
6545
  }, {
5370
6546
  key: "executeValidate",
5371
6547
  value: function executeValidate(path, src) {
6548
+ var customValidationErrors = this.executeCustomValidateFunctions(src, this.customValidateFunctions, {
6549
+ path: path
6550
+ });
5372
6551
  if (this.opt && (src === null || src === undefined)) {
5373
- return false;
6552
+ return customValidationErrors.length > 0 ? _defineProperty({}, path, customValidationErrors) : false;
5374
6553
  }
5375
- if (typeof src !== "boolean") {
6554
+ if (typeof src !== "string") {
5376
6555
  return _defineProperty({}, path, [{
5377
- message: "Expected 'boolean', got '".concat(_typeof(src), "'"),
6556
+ message: "Expected 'string', got '".concat(_typeof(src), "'"),
5378
6557
  value: src
5379
6558
  }]);
5380
6559
  }
5381
- return false;
6560
+ return _defineProperty({}, path, [].concat(_toConsumableArray(customValidationErrors), [{
6561
+ // Which languages exist is in another module, so this cannot be
6562
+ // answered here — the same shape `keyOf` and `route` use, resolved by
6563
+ // `resolveSchemaSourceFixes` against the settings module.
6564
+ fixes: ["locale:check-locale"],
6565
+ message: "Did not validate locale. This error (locale:check-locale) should typically be processed by Val internally. Seeing this error most likely means you have a Val version mismatch.",
6566
+ value: {
6567
+ locale: src,
6568
+ sourcePath: path
6569
+ }
6570
+ }]));
5382
6571
  }
5383
6572
  }, {
5384
6573
  key: "executeAssert",
@@ -5389,45 +6578,42 @@ var BooleanSchema = /*#__PURE__*/function (_Schema) {
5389
6578
  data: src
5390
6579
  };
5391
6580
  }
5392
- if (src === null) {
5393
- return {
5394
- success: false,
5395
- errors: _defineProperty({}, path, [{
5396
- message: "Expected 'boolean', got 'null'",
5397
- typeError: true
5398
- }])
5399
- };
5400
- }
5401
- if (typeof src !== "boolean") {
6581
+ if (typeof src === "string") {
5402
6582
  return {
5403
- success: false,
5404
- errors: _defineProperty({}, path, [{
5405
- message: "Expected 'boolean', got '".concat(_typeof(src), "'"),
5406
- typeError: true
5407
- }])
6583
+ success: true,
6584
+ data: src
5408
6585
  };
5409
6586
  }
5410
6587
  return {
5411
- success: true,
5412
- data: src
6588
+ success: false,
6589
+ errors: _defineProperty({}, path, [{
6590
+ message: "Expected 'string', got '".concat(_typeof(src), "'"),
6591
+ typeError: true
6592
+ }])
5413
6593
  };
5414
6594
  }
5415
6595
  }, {
5416
6596
  key: "nullable",
5417
6597
  value: function nullable() {
5418
- return new BooleanSchema(true, [], this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
6598
+ // Asserted whole, exactly as `RouteSchema.nullable` does: the source type
6599
+ // widens by `null` while the caller's validators and preview stay typed
6600
+ // against the value, and `executeValidate` returns before either sees one.
6601
+ return new LocaleSchema(true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
5419
6602
  }
5420
6603
  }, {
5421
6604
  key: "readonly",
5422
6605
  value: function readonly() {
5423
- var isReadonly = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
5424
- return new BooleanSchema(this.opt, this.customValidateFunctions, isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
6606
+ return new LocaleSchema(this.opt, this.customValidateFunctions, true, this.isHidden, this.description, this.renderInput, this.previewInput);
5425
6607
  }
5426
6608
  }, {
5427
6609
  key: "hidden",
5428
6610
  value: function hidden() {
5429
- var isHidden = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
5430
- return new BooleanSchema(this.opt, this.customValidateFunctions, this.isReadonly, isHidden, this.description, this.renderInput, this.previewInput);
6611
+ return new LocaleSchema(this.opt, this.customValidateFunctions, this.isReadonly, true, this.description, this.renderInput, this.previewInput);
6612
+ }
6613
+ }, {
6614
+ key: "isLocaleField",
6615
+ value: function isLocaleField() {
6616
+ return true;
5431
6617
  }
5432
6618
  }, {
5433
6619
  key: "executeCustomValidateAt",
@@ -5438,27 +6624,33 @@ var BooleanSchema = /*#__PURE__*/function (_Schema) {
5438
6624
  }
5439
6625
 
5440
6626
  /**
5441
- * How this field is laid out in the editor when it is the item of an array
5442
- * or record: `{ as: "inline" }` renders the field itself inside each row,
5443
- * instead of a preview row that navigates to it.
5444
- *
6627
+ * How this field is laid out where it is the item of an array or a record.
5445
6628
  * Static configuration, not a callback — see `render.ts`.
6629
+ *
6630
+ * @example
6631
+ * const schema = s.array(s.locale().render({ as: "inline" }));
6632
+ * export default c.define("/example.val.ts", schema, ["nb-NO", "en-US"]);
5446
6633
  */
5447
6634
  }, {
5448
6635
  key: "render",
5449
6636
  value: function render(input) {
5450
- return new BooleanSchema(this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, input, this.previewInput);
6637
+ return new LocaleSchema(this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, input, this.previewInput);
5451
6638
  }
5452
6639
 
5453
6640
  /**
5454
- * How this VALUE is shown where a preview of it is needed — a row in a
5455
- * sortable list, a reference dropdown, a search hit. Never how the field
6641
+ * How this VALUE is shown where a preview of it is needed. Never how the field
5456
6642
  * itself is edited (that is `render`). See `preview.ts`.
6643
+ *
6644
+ * @example
6645
+ * const schema = s.array(
6646
+ * s.locale().preview(({ val }) => ({ title: val })),
6647
+ * );
6648
+ * export default c.define("/example.val.ts", schema, ["nb-NO", "en-US"]);
5457
6649
  */
5458
6650
  }, {
5459
6651
  key: "preview",
5460
6652
  value: function preview(select) {
5461
- return new BooleanSchema(this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, select);
6653
+ return new LocaleSchema(this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, select);
5462
6654
  }
5463
6655
  }, {
5464
6656
  key: "executePreviewItem",
@@ -5480,7 +6672,7 @@ var BooleanSchema = /*#__PURE__*/function (_Schema) {
5480
6672
  value: function executeSerialize() {
5481
6673
  var _this$renderInput, _this$customValidateF;
5482
6674
  return {
5483
- type: "boolean",
6675
+ type: "locale",
5484
6676
  render: (_this$renderInput = this.renderInput) !== null && _this$renderInput !== void 0 ? _this$renderInput : undefined,
5485
6677
  preview: this.previewInput ? true : undefined,
5486
6678
  opt: this.opt,
@@ -5497,71 +6689,421 @@ var BooleanSchema = /*#__PURE__*/function (_Schema) {
5497
6689
  }
5498
6690
  }]);
5499
6691
  }(Schema);
5500
- var _boolean = function _boolean() {
5501
- return new BooleanSchema();
5502
- };
5503
6692
 
5504
6693
  /**
5505
- * An object whose keys are all optional: absent means unset.
6694
+ * Define one of the project's languages.
5506
6695
  *
5507
- * Both the settings module itself and each of its sections are this. See
5508
- * {@link SettingsSource} for why settings cannot be an object schema, and
5509
- * {@link settings} for what a settings module holds.
6696
+ * The languages themselves are declared in the settings module, under
6697
+ * `locales.available` — this says only that a value is one of them.
5510
6698
  *
5511
- * There is no `describe`, `validate`, `render` or `preview` on purpose: the
5512
- * shape is Val's, not the schema author's — `s.settings()` takes no arguments —
5513
- * and the Studio renders each section with a UI built for it.
6699
+ * @example // a field: everything under this object is in this language
6700
+ * const schema = s.object({ locale: s.locale(), title: s.string() });
6701
+ *
6702
+ * @example // a key: one entry per language
6703
+ * const schema = s.record(s.locale(), s.object({ title: s.string() }));
5514
6704
  */
5515
- var SettingsSchema = /*#__PURE__*/function (_Schema) {
5516
- function SettingsSchema(items) {
6705
+ var locale = function locale() {
6706
+ return new LocaleSchema();
6707
+ };
6708
+
6709
+ var BooleanSchema = /*#__PURE__*/function (_Schema) {
6710
+ function BooleanSchema() {
5517
6711
  var _this;
5518
- var opt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
6712
+ var opt = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
6713
+ var customValidateFunctions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
5519
6714
  var isReadonly = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
5520
6715
  var isHidden = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
5521
6716
  var description = arguments.length > 4 ? arguments[4] : undefined;
5522
- _classCallCheck(this, SettingsSchema);
5523
- _this = _callSuper(this, SettingsSchema);
5524
- _this.items = items;
6717
+ var renderInput = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : null;
6718
+ var previewInput = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : null;
6719
+ _classCallCheck(this, BooleanSchema);
6720
+ _this = _callSuper(this, BooleanSchema);
5525
6721
  _this.opt = opt;
6722
+ _this.customValidateFunctions = customValidateFunctions;
5526
6723
  _this.isReadonly = isReadonly;
5527
6724
  _this.isHidden = isHidden;
5528
6725
  _this.description = description;
6726
+ _this.renderInput = renderInput;
6727
+ _this.previewInput = previewInput;
5529
6728
  return _this;
5530
6729
  }
5531
- _inherits(SettingsSchema, _Schema);
5532
- return _createClass(SettingsSchema, [{
6730
+
6731
+ /**
6732
+ * Describe this field.
6733
+ *
6734
+ * The description is shown next to the field's label in the Val editor, so
6735
+ * it is where you say what an editor needs to know but the field name cannot
6736
+ * carry. It also travels in the serialized schema, which is what the AI
6737
+ * assistant and the MCP tools read.
6738
+ *
6739
+ * Pass `null` to clear a description set earlier.
6740
+ *
6741
+ * @example
6742
+ * const schema = s.boolean().describe("Show this item in the main menu");
6743
+ * export default c.define("/example.val.ts", schema, true);
6744
+ */
6745
+ _inherits(BooleanSchema, _Schema);
6746
+ return _createClass(BooleanSchema, [{
6747
+ key: "describe",
6748
+ value: function describe(description) {
6749
+ return new BooleanSchema(this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.renderInput, this.previewInput);
6750
+ }
6751
+
6752
+ /**
6753
+ * Add a custom validation rule to this field.
6754
+ *
6755
+ * The function is called with the field's value and returns `false` when the
6756
+ * value is fine, or a STRING with the message to show when it is not. Call it
6757
+ * more than once to add more rules — they all run, and every message is
6758
+ * reported.
6759
+ *
6760
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
6761
+ * when the value is fine, and `true` is not one of the two answers.
6762
+ *
6763
+ * Validation runs in the Studio as you type, in `npx val validate` and
6764
+ * before a publish.
6765
+ *
6766
+ * @example
6767
+ * const schema = s.boolean().validate((val) =>
6768
+ * val === true ? false : "This has to be on for now",
6769
+ * );
6770
+ * export default c.define("/example.val.ts", schema, true);
6771
+ */
6772
+ }, {
6773
+ key: "validate",
6774
+ value: function validate(validationFunction) {
6775
+ return new BooleanSchema(this.opt, [].concat(_toConsumableArray(this.customValidateFunctions), [validationFunction]), this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
6776
+ }
6777
+ }, {
5533
6778
  key: "executeValidate",
5534
6779
  value: function executeValidate(path, src) {
5535
- var error = false;
5536
- if (src === null || src === undefined) {
5537
- if (this.opt) {
5538
- return false;
5539
- }
5540
- return _defineProperty({}, path, [{
5541
- message: "Expected 'object', got '".concat(src, "'")
5542
- }]);
6780
+ if (this.opt && (src === null || src === undefined)) {
6781
+ return false;
5543
6782
  }
5544
- if (_typeof(src) !== "object") {
6783
+ if (typeof src !== "boolean") {
5545
6784
  return _defineProperty({}, path, [{
5546
- message: "Expected 'object', got '".concat(_typeof(src), "'")
6785
+ message: "Expected 'boolean', got '".concat(_typeof(src), "'"),
6786
+ value: src
5547
6787
  }]);
5548
6788
  }
5549
- if (Array.isArray(src)) {
5550
- return _defineProperty({}, path, [{
5551
- message: "Expected 'object', got 'array'"
5552
- }]);
6789
+ return false;
6790
+ }
6791
+ }, {
6792
+ key: "executeAssert",
6793
+ value: function executeAssert(path, src) {
6794
+ if (this.opt && src === null) {
6795
+ return {
6796
+ success: true,
6797
+ data: src
6798
+ };
5553
6799
  }
5554
- for (var _i = 0, _Object$entries = Object.entries(this.items); _i < _Object$entries.length; _i++) {
5555
- var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
5556
- _key = _Object$entries$_i[0],
5557
- schema = _Object$entries$_i[1];
5558
- // Absent is the unset value, so there is nothing to validate. A key that
5559
- // is present as `undefined` is the same statement written differently.
5560
- if (src[_key] === undefined) {
5561
- continue;
5562
- }
5563
- var subPath = createValPathOfItem(path, _key);
5564
- if (!subPath) {
6800
+ if (src === null) {
6801
+ return {
6802
+ success: false,
6803
+ errors: _defineProperty({}, path, [{
6804
+ message: "Expected 'boolean', got 'null'",
6805
+ typeError: true
6806
+ }])
6807
+ };
6808
+ }
6809
+ if (typeof src !== "boolean") {
6810
+ return {
6811
+ success: false,
6812
+ errors: _defineProperty({}, path, [{
6813
+ message: "Expected 'boolean', got '".concat(_typeof(src), "'"),
6814
+ typeError: true
6815
+ }])
6816
+ };
6817
+ }
6818
+ return {
6819
+ success: true,
6820
+ data: src
6821
+ };
6822
+ }
6823
+ }, {
6824
+ key: "nullable",
6825
+ value: function nullable() {
6826
+ return new BooleanSchema(true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
6827
+ }
6828
+ }, {
6829
+ key: "readonly",
6830
+ value: function readonly() {
6831
+ var isReadonly = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
6832
+ return new BooleanSchema(this.opt, this.customValidateFunctions, isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
6833
+ }
6834
+ }, {
6835
+ key: "hidden",
6836
+ value: function hidden() {
6837
+ var isHidden = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
6838
+ return new BooleanSchema(this.opt, this.customValidateFunctions, this.isReadonly, isHidden, this.description, this.renderInput, this.previewInput);
6839
+ }
6840
+ }, {
6841
+ key: "executeCustomValidateAt",
6842
+ value: function executeCustomValidateAt(path, src) {
6843
+ return this.executeCustomValidateFunctions(src, this.customValidateFunctions, {
6844
+ path: path
6845
+ });
6846
+ }
6847
+
6848
+ /**
6849
+ * How this field is laid out in the editor when it is the item of an array
6850
+ * or record: `{ as: "inline" }` renders the field itself inside each row,
6851
+ * instead of a preview row that navigates to it.
6852
+ *
6853
+ * Static configuration, not a callback — see `render.ts`.
6854
+ *
6855
+ * @example
6856
+ * const schema = s.array(s.boolean().render({ as: "inline" }));
6857
+ * export default c.define("/example.val.ts", schema, [true, false]);
6858
+ */
6859
+ }, {
6860
+ key: "render",
6861
+ value: function render(input) {
6862
+ return new BooleanSchema(this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, input, this.previewInput);
6863
+ }
6864
+
6865
+ /**
6866
+ * How this VALUE is shown where a preview of it is needed — a row in a
6867
+ * sortable list, a reference dropdown, a search hit. Never how the field
6868
+ * itself is edited (that is `render`). See `preview.ts`.
6869
+ *
6870
+ * @example
6871
+ * const schema = s.array(
6872
+ * s.boolean().preview(({ val }) => ({ title: val ? "On" : "Off" })),
6873
+ * );
6874
+ * export default c.define("/example.val.ts", schema, [true, false]);
6875
+ */
6876
+ }, {
6877
+ key: "preview",
6878
+ value: function preview(select) {
6879
+ return new BooleanSchema(this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, select);
6880
+ }
6881
+ }, {
6882
+ key: "executePreviewItem",
6883
+ value: function executePreviewItem(src) {
6884
+ if (this.previewInput === null) {
6885
+ return null;
6886
+ }
6887
+ return this.previewInput({
6888
+ val: src
6889
+ });
6890
+ }
6891
+ }, {
6892
+ key: "declaresItemPreview",
6893
+ value: function declaresItemPreview() {
6894
+ return this.previewInput !== null;
6895
+ }
6896
+ }, {
6897
+ key: "executeSerialize",
6898
+ value: function executeSerialize() {
6899
+ var _this$renderInput, _this$customValidateF;
6900
+ return {
6901
+ type: "boolean",
6902
+ render: (_this$renderInput = this.renderInput) !== null && _this$renderInput !== void 0 ? _this$renderInput : undefined,
6903
+ preview: this.previewInput ? true : undefined,
6904
+ opt: this.opt,
6905
+ customValidate: this.customValidateFunctions && ((_this$customValidateF = this.customValidateFunctions) === null || _this$customValidateF === void 0 ? void 0 : _this$customValidateF.length) > 0,
6906
+ readonly: this.isReadonly,
6907
+ hidden: this.isHidden,
6908
+ description: this.description
6909
+ };
6910
+ }
6911
+ }, {
6912
+ key: "executePreview",
6913
+ value: function executePreview() {
6914
+ return {};
6915
+ }
6916
+ }]);
6917
+ }(Schema);
6918
+ var _boolean = function _boolean() {
6919
+ return new BooleanSchema();
6920
+ };
6921
+
6922
+ /**
6923
+ * Language tags, and what makes one wrong.
6924
+ *
6925
+ * Val does not ship a list of locales. Which languages a project publishes is a
6926
+ * property of that project, so it is declared in the settings module — see
6927
+ * `LocalesSettingsSource` — and everything downstream reads it from there.
6928
+ *
6929
+ * What Val does own is the SPELLING. A tag is checked against BCP 47
6930
+ * (RFC 5646) through `Intl.getCanonicalLocales`, which is the same
6931
+ * implementation the browser, `<html lang>` and every `Intl` constructor use.
6932
+ * Delegating rather than writing a matcher is the point: a tag this accepts is
6933
+ * a tag those accept.
6934
+ */
6935
+
6936
+ /**
6937
+ * What is wrong with `tag` as a language tag, or `false` if nothing is.
6938
+ *
6939
+ * Canonical form, not merely parseable, and the difference is what makes this
6940
+ * worth checking at all. `nb-no` parses; `Intl.DisplayNames` and a
6941
+ * case-sensitive comparison against a stored key do not agree about it. Since a
6942
+ * locale is compared as a string everywhere — a record key, a URL segment, a
6943
+ * value in content — one spelling has to win, and the canonical one is the
6944
+ * spelling every other tool already produces.
6945
+ *
6946
+ * The message names the canonical form where there is one, because "not
6947
+ * canonical" is not an instruction and `nb-NO` is.
6948
+ */
6949
+ function localeTagError(tag) {
6950
+ var canonical;
6951
+ try {
6952
+ canonical = Intl.getCanonicalLocales(tag);
6953
+ } catch (_unused) {
6954
+ // RangeError, for anything the grammar rejects: an underscore instead of a
6955
+ // hyphen (`nb_NO`, which is POSIX rather than BCP 47), an empty subtag, a
6956
+ // subtag of the wrong length.
6957
+ return "'".concat(tag, "' is not a language tag. Language then region, separated by a hyphen \u2014 'nb-NO', not 'nb_NO'");
6958
+ }
6959
+ if (canonical.length !== 1) {
6960
+ // A comma-separated list, or something that canonicalised to nothing.
6961
+ return "'".concat(tag, "' is not a single language tag");
6962
+ }
6963
+ if (canonical[0] !== tag) {
6964
+ return "'".concat(tag, "' is not canonical. Write it as '").concat(canonical[0], "'");
6965
+ }
6966
+ return false;
6967
+ }
6968
+
6969
+ /**
6970
+ * A language tag, as content holds it.
6971
+ *
6972
+ * A *flavoured* string rather than a union or a strict brand. Which languages a
6973
+ * project has is declared in its settings module, where the project can change
6974
+ * it, so there is no set for a type to enumerate — and a strict brand would make
6975
+ * `"nb-NO"` unassignable, which would mean writing every locale in a `.val.ts`
6976
+ * through a constructor. Content stays plain data.
6977
+ *
6978
+ * So this carries no compile-time guarantee, and is not meant to. It names the
6979
+ * concept where a bare `string` would say nothing, and the guarantee is the
6980
+ * `locale:check-locale` validation against `locales.available`.
6981
+ */
6982
+
6983
+ /**
6984
+ * The locale a stored value means, or `null` if it means none of them.
6985
+ *
6986
+ * A lookup against the project's languages, and today that is all it is. It
6987
+ * stays a named function because it is the one place the question is asked —
6988
+ * the Studio, the validation worker and the server all come here rather than
6989
+ * comparing strings themselves, so they cannot disagree about what a stored
6990
+ * value is.
6991
+ */
6992
+ function localeOfValue(value, available) {
6993
+ return available.includes(value) ? value : null;
6994
+ }
6995
+
6996
+ /**
6997
+ * A rule about a section that its individual fields cannot state.
6998
+ *
6999
+ * Paths are WITHIN the section, so an error lands on the value it is about —
7000
+ * including a row of a list, which is what makes this different from a
7001
+ * `.validate()` on the field.
7002
+ */
7003
+
7004
+ /**
7005
+ * Every section's rules, by the name that travels in the serialized schema.
7006
+ *
7007
+ * Typed with `| undefined` on the value rather than as a `Partial`: the whole
7008
+ * point is that a lookup can miss, because the name comes off a serialized
7009
+ * schema that may have been written by a newer Val than the one reading it.
7010
+ */
7011
+ var sectionValidators = {
7012
+ locales: localesSectionErrors
7013
+ };
7014
+
7015
+ /**
7016
+ * An object whose keys are all optional: absent means unset.
7017
+ *
7018
+ * Both the settings module itself and each of its sections are this. See
7019
+ * {@link SettingsSource} for why settings cannot be an object schema, and
7020
+ * {@link settings} for what a settings module holds.
7021
+ *
7022
+ * There is no `describe`, `validate`, `render` or `preview` on purpose: the
7023
+ * shape is Val's, not the schema author's — `s.settings()` takes no arguments —
7024
+ * and the Studio renders each section with a UI built for it.
7025
+ */
7026
+ var SettingsSchema = /*#__PURE__*/function (_Schema) {
7027
+ function SettingsSchema(items) {
7028
+ var _this;
7029
+ var opt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
7030
+ var isReadonly = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
7031
+ var isHidden = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
7032
+ var description = arguments.length > 4 ? arguments[4] : undefined;
7033
+ var
7034
+ /**
7035
+ * Which section this is, where Val has rules about it beyond its keys.
7036
+ *
7037
+ * Not the `validate` the class deliberately does not offer: that one is the
7038
+ * schema author's, and `s.settings()` takes no arguments to declare it
7039
+ * with. This names a rule Val states about a shape Val owns — no language
7040
+ * declared twice in `locales.available` is true of every project, and
7041
+ * there is nowhere else it can be said. A single value's rules stay on its
7042
+ * own schema; this is for the ones that need to see more than one value,
7043
+ * or to report on a row of a list.
7044
+ *
7045
+ * A NAME rather than the function, so it survives serialization: the schema
7046
+ * reaches the Studio's validation worker as JSON, and a closure would be
7047
+ * dropped on the way — which is how these rules once went missing there.
7048
+ */
7049
+ section = arguments.length > 5 ? arguments[5] : undefined;
7050
+ _classCallCheck(this, SettingsSchema);
7051
+ _this = _callSuper(this, SettingsSchema);
7052
+ _this.items = items;
7053
+ _this.opt = opt;
7054
+ _this.isReadonly = isReadonly;
7055
+ _this.isHidden = isHidden;
7056
+ _this.description = description;
7057
+ _this.section = section;
7058
+ return _this;
7059
+ }
7060
+
7061
+ /**
7062
+ * This section's rules, or `undefined` where there are none to run.
7063
+ *
7064
+ * A name this Val does not know means a schema written by a newer one. Its
7065
+ * per-field validation still runs; only the section's own rule is missed,
7066
+ * which is the mild half of the failure and better than refusing the schema.
7067
+ */
7068
+ _inherits(SettingsSchema, _Schema);
7069
+ return _createClass(SettingsSchema, [{
7070
+ key: "sectionValidate",
7071
+ value: function sectionValidate() {
7072
+ return this.section === undefined ? undefined : sectionValidators[this.section];
7073
+ }
7074
+ }, {
7075
+ key: "executeValidate",
7076
+ value: function executeValidate(path, src) {
7077
+ var error = false;
7078
+ if (src === null || src === undefined) {
7079
+ if (this.opt) {
7080
+ return false;
7081
+ }
7082
+ return _defineProperty({}, path, [{
7083
+ message: "Expected 'object', got '".concat(src, "'")
7084
+ }]);
7085
+ }
7086
+ if (_typeof(src) !== "object") {
7087
+ return _defineProperty({}, path, [{
7088
+ message: "Expected 'object', got '".concat(_typeof(src), "'")
7089
+ }]);
7090
+ }
7091
+ if (Array.isArray(src)) {
7092
+ return _defineProperty({}, path, [{
7093
+ message: "Expected 'object', got 'array'"
7094
+ }]);
7095
+ }
7096
+ for (var _i = 0, _Object$entries = Object.entries(this.items); _i < _Object$entries.length; _i++) {
7097
+ var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
7098
+ _key = _Object$entries$_i[0],
7099
+ schema = _Object$entries$_i[1];
7100
+ // Absent is the unset value, so there is nothing to validate. A key that
7101
+ // is present as `undefined` is the same statement written differently.
7102
+ if (src[_key] === undefined) {
7103
+ continue;
7104
+ }
7105
+ var subPath = createValPathOfItem(path, _key);
7106
+ if (!subPath) {
5565
7107
  error = this.appendValidationError(error, path, "Internal error: could not create path at ".concat(!path && typeof path === "string" ? "<empty string>" : path, " at key ").concat(_key),
5566
7108
  // Should! never happen
5567
7109
  src);
@@ -5582,6 +7124,39 @@ var SettingsSchema = /*#__PURE__*/function (_Schema) {
5582
7124
  }).join(", ")), src, true);
5583
7125
  }
5584
7126
  }
7127
+ var sectionValidate = this.sectionValidate();
7128
+ if (sectionValidate) {
7129
+ var _iterator = _createForOfIteratorHelper(sectionValidate(src)),
7130
+ _step;
7131
+ try {
7132
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
7133
+ var _ref4;
7134
+ var _step$value = _step.value,
7135
+ keys = _step$value.path,
7136
+ message = _step$value.message;
7137
+ var _subPath = path;
7138
+ var _iterator2 = _createForOfIteratorHelper(keys),
7139
+ _step2;
7140
+ try {
7141
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
7142
+ var _key3 = _step2.value;
7143
+ _subPath = _subPath && createValPathOfItem(_subPath, _key3);
7144
+ }
7145
+ } catch (err) {
7146
+ _iterator2.e(err);
7147
+ } finally {
7148
+ _iterator2.f();
7149
+ }
7150
+ error = this.appendValidationError(error, // Falls back to the section itself, which is where an error belongs
7151
+ // when the value it is about is not addressable.
7152
+ (_ref4 = _subPath) !== null && _ref4 !== void 0 ? _ref4 : path, message, src);
7153
+ }
7154
+ } catch (err) {
7155
+ _iterator.e(err);
7156
+ } finally {
7157
+ _iterator.f();
7158
+ }
7159
+ }
5585
7160
  return error;
5586
7161
  }
5587
7162
  }, {
@@ -5627,17 +7202,17 @@ var SettingsSchema = /*#__PURE__*/function (_Schema) {
5627
7202
  }, {
5628
7203
  key: "nullable",
5629
7204
  value: function nullable() {
5630
- return new SettingsSchema(this.items, true, this.isReadonly, this.isHidden, this.description);
7205
+ return new SettingsSchema(this.items, true, this.isReadonly, this.isHidden, this.description, this.section);
5631
7206
  }
5632
7207
  }, {
5633
7208
  key: "readonly",
5634
7209
  value: function readonly() {
5635
- return new SettingsSchema(this.items, this.opt, true, this.isHidden, this.description);
7210
+ return new SettingsSchema(this.items, this.opt, true, this.isHidden, this.description, this.section);
5636
7211
  }
5637
7212
  }, {
5638
7213
  key: "hidden",
5639
7214
  value: function hidden() {
5640
- return new SettingsSchema(this.items, this.opt, this.isReadonly, true, this.description);
7215
+ return new SettingsSchema(this.items, this.opt, this.isReadonly, true, this.description, this.section);
5641
7216
  }
5642
7217
  }, {
5643
7218
  key: "executeCustomValidateAt",
@@ -5652,16 +7227,17 @@ var SettingsSchema = /*#__PURE__*/function (_Schema) {
5652
7227
  value: function executeSerialize() {
5653
7228
  return {
5654
7229
  type: "settings",
5655
- items: Object.fromEntries(Object.entries(this.items).map(function (_ref4) {
5656
- var _ref5 = _slicedToArray(_ref4, 2),
5657
- key = _ref5[0],
5658
- schema = _ref5[1];
7230
+ items: Object.fromEntries(Object.entries(this.items).map(function (_ref5) {
7231
+ var _ref6 = _slicedToArray(_ref5, 2),
7232
+ key = _ref6[0],
7233
+ schema = _ref6[1];
5659
7234
  return [key, schema["executeSerialize"]()];
5660
7235
  })),
5661
7236
  opt: this.opt,
5662
7237
  readonly: this.isReadonly,
5663
7238
  hidden: this.isHidden,
5664
- description: this.description
7239
+ description: this.description,
7240
+ section: this.section
5665
7241
  };
5666
7242
  }
5667
7243
  }, {
@@ -5671,19 +7247,19 @@ var SettingsSchema = /*#__PURE__*/function (_Schema) {
5671
7247
  if (src === null) {
5672
7248
  return res;
5673
7249
  }
5674
- for (var _key3 in this.items) {
5675
- var itemSrc = src[_key3];
7250
+ for (var _key4 in this.items) {
7251
+ var itemSrc = src[_key4];
5676
7252
  if (itemSrc === null || itemSrc === undefined) {
5677
7253
  continue;
5678
7254
  }
5679
- var subPath = unsafeCreateSourcePath(sourcePath, _key3);
7255
+ var subPath = unsafeCreateSourcePath(sourcePath, _key4);
5680
7256
  if (scope !== undefined && !scope.wantsUnder(subPath)) {
5681
7257
  continue;
5682
7258
  }
5683
- var itemResult = this.items[_key3]["executePreview"](subPath, itemSrc, scope);
7259
+ var itemResult = this.items[_key4]["executePreview"](subPath, itemSrc, scope);
5684
7260
  for (var keyS in itemResult) {
5685
- var _key4 = keyS;
5686
- res[_key4] = itemResult[_key4];
7261
+ var _key5 = keyS;
7262
+ res[_key5] = itemResult[_key5];
5687
7263
  }
5688
7264
  }
5689
7265
  return res;
@@ -5727,11 +7303,58 @@ function settings() {
5727
7303
  assistant: new SettingsSchema({
5728
7304
  enabled: _boolean().nullable().describe("Whether editors have an assistant in this project. Unset means nobody has decided: it is offered, and turned on when someone accepts."),
5729
7305
  context: string().multiline().maxLength(ASSISTANT_SETTINGS_MAX_LENGTH).nullable().describe("Background the assistant would otherwise have to guess: what this site is, who runs it, names and spellings that matter."),
5730
- tone: string().multiline().maxLength(ASSISTANT_SETTINGS_MAX_LENGTH).nullable().describe("How the assistant should write when it writes content: formal or playful, British or American, how headings are cased.")
5731
- })
7306
+ tone: string().multiline().maxLength(ASSISTANT_SETTINGS_MAX_LENGTH).nullable().describe("How the assistant should write when it writes content: formal or playful, British or American, how headings are cased."),
7307
+ translation: record(locale(), string().multiline().maxLength(ASSISTANT_SETTINGS_MAX_LENGTH).nullable()).nullable().describe("How to translate into each language: dialect, formality, and the words that stay untranslated.")
7308
+ }),
7309
+ locales: new SettingsSchema({
7310
+ // Nullable because the source type is, and because the Studio writes
7311
+ // `null` for every sibling the first time a section is created.
7312
+ available: array(string()).nullable().describe("The languages this project publishes, as BCP 47 tags: en-US, nb-NO. The order is kept: it is the order of the Studio's picker and of a locale-keyed record's rows.")
7313
+ }, false, false, false, undefined, "locales")
5732
7314
  });
5733
7315
  }
5734
7316
 
7317
+ /**
7318
+ * The rules about `locales` that need to see the whole list.
7319
+ *
7320
+ * Two of them, and both report on the ENTRY rather than the list: a tag has to
7321
+ * be spelled canonically, and a language cannot be declared twice. Neither can
7322
+ * live on the item schema — the item sees one string, and "twice" is a fact
7323
+ * about its siblings — and neither can be a `.validate()` on the array either,
7324
+ * since that reports one message on the array itself rather than on the row to
7325
+ * delete.
7326
+ *
7327
+ * Reads defensively rather than asserting the source's type. A settings module
7328
+ * is a file someone edits by hand, and validation runs against whatever is in
7329
+ * it — including the shapes the per-key pass is, in the same breath, reporting.
7330
+ */
7331
+ function localesSectionErrors(src) {
7332
+ var errors = [];
7333
+ var raw = Array.isArray(src["available"]) ? src["available"] : [];
7334
+ var seen = new Set();
7335
+ for (var i = 0; i < raw.length; i++) {
7336
+ var tag = raw[i];
7337
+ if (typeof tag !== "string") {
7338
+ // The item's own schema has already said what is wrong with it.
7339
+ continue;
7340
+ }
7341
+ var spelling = localeTagError(tag);
7342
+ if (spelling !== false) {
7343
+ errors.push({
7344
+ path: ["available", i],
7345
+ message: spelling
7346
+ });
7347
+ } else if (seen.has(tag)) {
7348
+ errors.push({
7349
+ path: ["available", i],
7350
+ message: "'".concat(tag, "' is declared twice")
7351
+ });
7352
+ }
7353
+ seen.add(tag);
7354
+ }
7355
+ return errors;
7356
+ }
7357
+
5735
7358
  /**
5736
7359
  * What may be WRITTEN as an external record's source, on top of the marker.
5737
7360
  *
@@ -5812,10 +7435,8 @@ function isArraySchema(schema) {
5812
7435
  // );
5813
7436
  // }
5814
7437
 
5815
- function isUnionSchema(schema
5816
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
5817
- ) {
5818
- return schema instanceof UnionSchema || _typeof(schema) === "object" && "type" in schema && schema.type === "union";
7438
+ function isDiscriminatedUnionSchema(schema) {
7439
+ return schema instanceof DiscriminatedUnionSchema || _typeof(schema) === "object" && "type" in schema && schema.type === "discriminated-union";
5819
7440
  }
5820
7441
  function isRichTextSchema(schema) {
5821
7442
  return schema instanceof RichTextSchema || _typeof(schema) === "object" && "type" in schema && schema.type === "richtext";
@@ -5921,27 +7542,18 @@ function resolvePath(path, valModule, schema) {
5921
7542
  source: resolvedSource
5922
7543
  }
5923
7544
  };
5924
- } else if (isUnionSchema(resolvedSchema)) {
5925
- var _key = resolvedSchema.key;
5926
- if (typeof _key !== "string") {
5927
- return {
5928
- v: {
5929
- path: origParts.map(function (p) {
5930
- if (!Number.isNaN(Number(p))) {
5931
- return p;
5932
- } else {
5933
- return JSON.stringify(p);
5934
- }
5935
- }).join("."),
5936
- // TODO: create a function generate path from parts (not sure if this always works)
5937
- schema: resolvedSchema,
5938
- source: resolvedSource
5939
- }
5940
- };
7545
+ } else if (isDiscriminatedUnionSchema(resolvedSchema)) {
7546
+ var _key = resolvedSchema instanceof DiscriminatedUnionSchema ? resolvedSchema["key"] : resolvedSchema.key;
7547
+ // A nullable union holding `null` has no variant to descend into, and
7548
+ // `null[key]` throws before any of the reporting below can run.
7549
+ if (resolvedSource === null || _typeof(resolvedSource) !== "object") {
7550
+ throw Error("Schema type error: expected discriminated union source to be an object, but got ".concat(resolvedSource === null ? "null" : _typeof(resolvedSource), " in path: ").concat(path));
5941
7551
  }
5942
7552
  var keyValue = resolvedSource[_key];
5943
- if (!keyValue) {
5944
- throw Error("Invalid path: union source ".concat(resolvedSchema, " did not have required key ").concat(_key, " in path: ").concat(path));
7553
+ // `undefined`, not falsy: `s.literal("")` is a legal tag, and a variant
7554
+ // carrying it was reported as a missing key.
7555
+ if (keyValue === undefined) {
7556
+ throw Error("Invalid path: discriminated union source ".concat(resolvedSchema, " did not have required key ").concat(_key, " in path: ").concat(path));
5945
7557
  }
5946
7558
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5947
7559
  var schemaOfUnionKey = resolvedSchema.items.find(
@@ -5951,7 +7563,7 @@ function resolvePath(path, valModule, schema) {
5951
7563
  return (child === null || child === void 0 || (_child$items = child.items) === null || _child$items === void 0 || (_child$items = _child$items[_key]) === null || _child$items === void 0 ? void 0 : _child$items.value) === keyValue;
5952
7564
  });
5953
7565
  if (!schemaOfUnionKey) {
5954
- throw Error("Invalid path: union schema ".concat(resolvedSchema, " did not have a child object with ").concat(_key, " of value ").concat(keyValue, " in path: ").concat(path));
7566
+ throw Error("Invalid path: discriminated union schema ".concat(resolvedSchema, " did not have a child object with ").concat(_key, " of value ").concat(keyValue, " in path: ").concat(path));
5955
7567
  }
5956
7568
  resolvedSchema = schemaOfUnionKey.items[part];
5957
7569
  resolvedSource = resolvedSource[part];
@@ -6171,31 +7783,25 @@ function safeResolvePath(path, valModule, schema) {
6171
7783
  source: resolvedSource
6172
7784
  }
6173
7785
  };
6174
- } else if (isUnionSchema(resolvedSchema)) {
6175
- var _key2 = resolvedSchema.key;
6176
- if (typeof _key2 !== "string") {
7786
+ } else if (isDiscriminatedUnionSchema(resolvedSchema)) {
7787
+ var _key2 = resolvedSchema instanceof DiscriminatedUnionSchema ? resolvedSchema["key"] : resolvedSchema.key;
7788
+ // See the note in `resolvePath`: a null union has no variant to descend
7789
+ // into, and this API promises a structured error rather than a throw.
7790
+ if (resolvedSource === null || _typeof(resolvedSource) !== "object") {
6177
7791
  return {
6178
7792
  v: {
6179
- status: "ok",
6180
- path: origParts.map(function (p) {
6181
- if (!Number.isNaN(Number(p))) {
6182
- return p;
6183
- } else {
6184
- return JSON.stringify(p);
6185
- }
6186
- }).join("."),
6187
- // TODO: create a function generate path from parts (not sure if this always works)
6188
- schema: resolvedSchema,
6189
- source: resolvedSource
7793
+ status: "error",
7794
+ message: "Schema type error: expected discriminated union source to be an object, but got ".concat(resolvedSource === null ? "null" : _typeof(resolvedSource), " in path: ").concat(path)
6190
7795
  }
6191
7796
  };
6192
7797
  }
6193
7798
  var keyValue = resolvedSource[_key2];
6194
- if (!keyValue) {
7799
+ // See the note in `resolvePath`: an empty-string tag is a present key.
7800
+ if (keyValue === undefined) {
6195
7801
  return {
6196
7802
  v: {
6197
7803
  status: "error",
6198
- message: "Invalid path: union source ".concat(resolvedSchema, " did not have required key ").concat(_key2, " in path: ").concat(path)
7804
+ message: "Invalid path: discriminated union source ".concat(resolvedSchema, " did not have required key ").concat(_key2, " in path: ").concat(path)
6199
7805
  }
6200
7806
  };
6201
7807
  }
@@ -6210,7 +7816,7 @@ function safeResolvePath(path, valModule, schema) {
6210
7816
  return {
6211
7817
  v: {
6212
7818
  status: "error",
6213
- message: "Invalid path: union schema ".concat(resolvedSchema, " did not have a child object with ").concat(_key2, " of value ").concat(keyValue, " in path: ").concat(path)
7819
+ message: "Invalid path: discriminated union schema ".concat(resolvedSchema, " did not have a child object with ").concat(_key2, " of value ").concat(keyValue, " in path: ").concat(path)
6214
7820
  }
6215
7821
  };
6216
7822
  }
@@ -6347,12 +7953,48 @@ var NumberSchema = /*#__PURE__*/function (_Schema) {
6347
7953
  _this.previewInput = previewInput;
6348
7954
  return _this;
6349
7955
  }
7956
+
7957
+ /**
7958
+ * Describe this field.
7959
+ *
7960
+ * The description is shown next to the field's label in the Val editor, so
7961
+ * it is where you say what an editor needs to know but the field name cannot
7962
+ * carry. It also travels in the serialized schema, which is what the AI
7963
+ * assistant and the MCP tools read.
7964
+ *
7965
+ * Pass `null` to clear a description set earlier.
7966
+ *
7967
+ * @example
7968
+ * const schema = s.number().describe("How many columns the grid uses");
7969
+ * export default c.define("/example.val.ts", schema, 3);
7970
+ */
6350
7971
  _inherits(NumberSchema, _Schema);
6351
7972
  return _createClass(NumberSchema, [{
6352
7973
  key: "describe",
6353
7974
  value: function describe(description) {
6354
7975
  return new NumberSchema(this.options, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.renderInput, this.previewInput);
6355
7976
  }
7977
+
7978
+ /**
7979
+ * Add a custom validation rule to this field.
7980
+ *
7981
+ * The function is called with the field's value and returns `false` when the
7982
+ * value is fine, or a STRING with the message to show when it is not. Call it
7983
+ * more than once to add more rules — they all run, and every message is
7984
+ * reported.
7985
+ *
7986
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
7987
+ * when the value is fine, and `true` is not one of the two answers.
7988
+ *
7989
+ * Validation runs in the Studio as you type, in `npx val validate` and
7990
+ * before a publish.
7991
+ *
7992
+ * @example
7993
+ * const schema = s.number().validate((val) =>
7994
+ * Number.isInteger(val) ? false : "Must be a whole number",
7995
+ * );
7996
+ * export default c.define("/example.val.ts", schema, 3);
7997
+ */
6356
7998
  }, {
6357
7999
  key: "validate",
6358
8000
  value: function validate(validationFunction) {
@@ -6396,63 +8038,348 @@ var NumberSchema = /*#__PURE__*/function (_Schema) {
6396
8038
  value: function executeAssert(path, src) {
6397
8039
  if (this.opt && src === null) {
6398
8040
  return {
6399
- success: true,
6400
- data: src
8041
+ success: true,
8042
+ data: src
8043
+ };
8044
+ }
8045
+ if (src === null) {
8046
+ return {
8047
+ success: false,
8048
+ errors: _defineProperty({}, path, [{
8049
+ message: "Expected 'number', got 'null'",
8050
+ typeError: true
8051
+ }])
8052
+ };
8053
+ }
8054
+ if (typeof src === "number") {
8055
+ return {
8056
+ success: true,
8057
+ data: src
8058
+ };
8059
+ }
8060
+ return {
8061
+ success: false,
8062
+ errors: _defineProperty({}, path, [{
8063
+ message: "Expected 'number', got '".concat(_typeof(src), "'"),
8064
+ typeError: true
8065
+ }])
8066
+ };
8067
+ }
8068
+ }, {
8069
+ key: "nullable",
8070
+ value: function nullable() {
8071
+ return new NumberSchema(this.options, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
8072
+ }
8073
+ }, {
8074
+ key: "readonly",
8075
+ value: function readonly() {
8076
+ var isReadonly = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
8077
+ return new NumberSchema(this.options, this.opt, this.customValidateFunctions, isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
8078
+ }
8079
+ }, {
8080
+ key: "hidden",
8081
+ value: function hidden() {
8082
+ var isHidden = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
8083
+ return new NumberSchema(this.options, this.opt, this.customValidateFunctions, this.isReadonly, isHidden, this.description, this.renderInput, this.previewInput);
8084
+ }
8085
+
8086
+ /**
8087
+ * Validate that the number is at most `max` (inclusive).
8088
+ *
8089
+ * @example
8090
+ * const schema = s.number().max(4);
8091
+ * export default c.define("/example.val.ts", schema, 3);
8092
+ */
8093
+ }, {
8094
+ key: "max",
8095
+ value: function max(_max) {
8096
+ return new NumberSchema(_objectSpread2(_objectSpread2({}, this.options), {}, {
8097
+ max: _max
8098
+ }), this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
8099
+ }
8100
+
8101
+ /**
8102
+ * Validate that the number is at least `min` (inclusive).
8103
+ *
8104
+ * @example
8105
+ * const schema = s.number().min(1).max(4);
8106
+ * export default c.define("/example.val.ts", schema, 3);
8107
+ */
8108
+ }, {
8109
+ key: "min",
8110
+ value: function min(_min) {
8111
+ return new NumberSchema(_objectSpread2(_objectSpread2({}, this.options), {}, {
8112
+ min: _min
8113
+ }), this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
8114
+ }
8115
+ }, {
8116
+ key: "executeCustomValidateAt",
8117
+ value: function executeCustomValidateAt(path, src) {
8118
+ return this.executeCustomValidateFunctions(src, this.customValidateFunctions, {
8119
+ path: path
8120
+ });
8121
+ }
8122
+
8123
+ /**
8124
+ * How this field is laid out in the editor when it is the item of an array
8125
+ * or record: `{ as: "inline" }` renders the field itself inside each row,
8126
+ * instead of a preview row that navigates to it.
8127
+ *
8128
+ * Static configuration, not a callback — see `render.ts`.
8129
+ *
8130
+ * @example
8131
+ * const schema = s.array(s.number().render({ as: "inline" }));
8132
+ * export default c.define("/example.val.ts", schema, [1, 2]);
8133
+ */
8134
+ }, {
8135
+ key: "render",
8136
+ value: function render(input) {
8137
+ return new NumberSchema(this.options, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, input, this.previewInput);
8138
+ }
8139
+
8140
+ /**
8141
+ * How this VALUE is shown where a preview of it is needed — a row in a
8142
+ * sortable list, a reference dropdown, a search hit. Never how the field
8143
+ * itself is edited (that is `render`). See `preview.ts`.
8144
+ *
8145
+ * @example
8146
+ * const schema = s.array(
8147
+ * s.number().preview(({ val }) => ({ title: String(val) })),
8148
+ * );
8149
+ * export default c.define("/example.val.ts", schema, [1, 2]);
8150
+ */
8151
+ }, {
8152
+ key: "preview",
8153
+ value: function preview(select) {
8154
+ return new NumberSchema(this.options, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, select);
8155
+ }
8156
+ }, {
8157
+ key: "executePreviewItem",
8158
+ value: function executePreviewItem(src) {
8159
+ if (this.previewInput === null) {
8160
+ return null;
8161
+ }
8162
+ return this.previewInput({
8163
+ val: src
8164
+ });
8165
+ }
8166
+ }, {
8167
+ key: "declaresItemPreview",
8168
+ value: function declaresItemPreview() {
8169
+ return this.previewInput !== null;
8170
+ }
8171
+ }, {
8172
+ key: "executeSerialize",
8173
+ value: function executeSerialize() {
8174
+ var _this$renderInput, _this$customValidateF;
8175
+ return {
8176
+ type: "number",
8177
+ render: (_this$renderInput = this.renderInput) !== null && _this$renderInput !== void 0 ? _this$renderInput : undefined,
8178
+ preview: this.previewInput ? true : undefined,
8179
+ options: this.options,
8180
+ opt: this.opt,
8181
+ customValidate: this.customValidateFunctions && ((_this$customValidateF = this.customValidateFunctions) === null || _this$customValidateF === void 0 ? void 0 : _this$customValidateF.length) > 0,
8182
+ readonly: this.isReadonly,
8183
+ hidden: this.isHidden,
8184
+ description: this.description
8185
+ };
8186
+ }
8187
+ }, {
8188
+ key: "executePreview",
8189
+ value: function executePreview() {
8190
+ return {};
8191
+ }
8192
+ }]);
8193
+ }(Schema);
8194
+ var number = function number(options) {
8195
+ return new NumberSchema(options);
8196
+ };
8197
+
8198
+ /**
8199
+ * One of a fixed set of strings.
8200
+ *
8201
+ * The values are the schema — there are no member schemas to descend into, so
8202
+ * this is a LEAF, like `s.string()` with a closed domain. That is the whole
8203
+ * difference from {@link DiscriminatedUnionSchema}, and the reason the two are
8204
+ * separate: everything that walks a schema tree has to recurse into a
8205
+ * discriminated union's variants and must not try to recurse into these.
8206
+ */
8207
+ var EnumSchema = /*#__PURE__*/function (_Schema) {
8208
+ /**
8209
+ * Type-only marker: as a record key, this declares the key set.
8210
+ *
8211
+ * An enum names every value it may take, so a record keyed by one holds
8212
+ * exactly those keys — the same claim `s.literal()` and `s.locale()` make.
8213
+ * `DiscriminatedUnionSchema` carries no marker: it discriminates objects and
8214
+ * cannot be a record key at all. See `LocaleSchema`.
8215
+ */
8216
+
8217
+ function EnumSchema(values) {
8218
+ var _this;
8219
+ var opt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
8220
+ var customValidateFunctions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
8221
+ var isReadonly = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
8222
+ var isHidden = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
8223
+ var description = arguments.length > 5 ? arguments[5] : undefined;
8224
+ var renderInput = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : null;
8225
+ var previewInput = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : null;
8226
+ _classCallCheck(this, EnumSchema);
8227
+ _this = _callSuper(this, EnumSchema);
8228
+ _this.values = values;
8229
+ _this.opt = opt;
8230
+ _this.customValidateFunctions = customValidateFunctions;
8231
+ _this.isReadonly = isReadonly;
8232
+ _this.isHidden = isHidden;
8233
+ _this.description = description;
8234
+ _this.renderInput = renderInput;
8235
+ _this.previewInput = previewInput;
8236
+ return _this;
8237
+ }
8238
+
8239
+ /**
8240
+ * Describe this field.
8241
+ *
8242
+ * The description is shown next to the field's label in the Val editor, so it is
8243
+ * where you say what an editor needs to know but the field name cannot carry. It
8244
+ * also travels in the serialized schema, which is what the AI assistant and the
8245
+ * MCP tools read.
8246
+ *
8247
+ * Pass `null` to clear a description set earlier.
8248
+ *
8249
+ * @example
8250
+ * const schema = s
8251
+ * .enum("draft", "published")
8252
+ * .describe("Only published pages are built");
8253
+ * export default c.define("/example.val.ts", schema, "draft");
8254
+ */
8255
+ _inherits(EnumSchema, _Schema);
8256
+ return _createClass(EnumSchema, [{
8257
+ key: "describe",
8258
+ value: function describe(description) {
8259
+ return new EnumSchema(this.values, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.renderInput, this.previewInput);
8260
+ }
8261
+
8262
+ /**
8263
+ * Add a custom validation rule to this field.
8264
+ *
8265
+ * The function is called with the field's value and returns `false` when the
8266
+ * value is fine, or a STRING with the message to show when it is not. Call it
8267
+ * more than once to add more rules — they all run, and every message is
8268
+ * reported.
8269
+ *
8270
+ * Validation runs in the Studio as you type, in `npx val validate` and before a
8271
+ * publish.
8272
+ *
8273
+ * @example
8274
+ * const schema = s
8275
+ * .enum("draft", "published")
8276
+ * .validate((val) =>
8277
+ * val === "published" ? "Publishing is frozen this week" : false,
8278
+ * );
8279
+ * export default c.define("/example.val.ts", schema, "draft");
8280
+ */
8281
+ }, {
8282
+ key: "validate",
8283
+ value: function validate(validationFunction) {
8284
+ return new EnumSchema(this.values, this.opt, this.customValidateFunctions.concat(validationFunction), this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
8285
+ }
8286
+ }, {
8287
+ key: "executeValidate",
8288
+ value: function executeValidate(path, src) {
8289
+ var customValidationErrors = this.executeCustomValidateFunctions(src, this.customValidateFunctions, {
8290
+ path: path
8291
+ });
8292
+ // The custom validators always report, whatever the structural answer is.
8293
+ var errors = customValidationErrors;
8294
+ var unknownSrc = src;
8295
+ if (this.opt && (unknownSrc === null || unknownSrc === undefined)) {
8296
+ return errors.length > 0 ? _defineProperty({}, path, errors) : false;
8297
+ }
8298
+ if (!Array.isArray(this.values) || this.values.length === 0) {
8299
+ errors.push({
8300
+ message: "An enum schema must have at least one value",
8301
+ schemaError: true
8302
+ });
8303
+ } else if (typeof unknownSrc !== "string") {
8304
+ errors.push({
8305
+ message: "Expected 'string', got '".concat(unknownSrc === null ? "null" : _typeof(unknownSrc), "'"),
8306
+ value: src,
8307
+ typeError: true
8308
+ });
8309
+ } else if (!this.values.includes(unknownSrc)) {
8310
+ errors.push({
8311
+ message: "Value must be one of the following: ".concat(this.values.map(function (value) {
8312
+ return "\"".concat(value, "\"");
8313
+ }).join(", ")),
8314
+ value: src
8315
+ });
8316
+ }
8317
+ return errors.length > 0 ? _defineProperty({}, path, errors) : false;
8318
+ }
8319
+ }, {
8320
+ key: "executeAssert",
8321
+ value: function executeAssert(path, src) {
8322
+ if (this.opt && src === null) {
8323
+ return {
8324
+ success: true,
8325
+ data: src
8326
+ };
8327
+ }
8328
+ var errors = {};
8329
+ if (typeof src !== "string") {
8330
+ errors[path] = [{
8331
+ message: "Expected 'string', got '".concat(src === null ? "null" : _typeof(src), "'"),
8332
+ typeError: true
8333
+ }];
8334
+ return {
8335
+ success: false,
8336
+ errors: errors
6401
8337
  };
6402
8338
  }
6403
- if (src === null) {
8339
+ if (!Array.isArray(this.values) || this.values.length === 0) {
8340
+ errors[path] = [{
8341
+ message: "An enum schema must have at least one value",
8342
+ schemaError: true
8343
+ }];
6404
8344
  return {
6405
8345
  success: false,
6406
- errors: _defineProperty({}, path, [{
6407
- message: "Expected 'number', got 'null'",
6408
- typeError: true
6409
- }])
8346
+ errors: errors
6410
8347
  };
6411
8348
  }
6412
- if (typeof src === "number") {
8349
+ if (!this.values.includes(src)) {
8350
+ errors[path] = [{
8351
+ message: "Expected one of ".concat(this.values.map(function (value) {
8352
+ return "'".concat(value, "'");
8353
+ }).join(", "), ", got '").concat(src, "'"),
8354
+ typeError: true
8355
+ }];
6413
8356
  return {
6414
- success: true,
6415
- data: src
8357
+ success: false,
8358
+ errors: errors
6416
8359
  };
6417
8360
  }
6418
8361
  return {
6419
- success: false,
6420
- errors: _defineProperty({}, path, [{
6421
- message: "Expected 'number', got '".concat(_typeof(src), "'"),
6422
- typeError: true
6423
- }])
8362
+ success: true,
8363
+ data: src
6424
8364
  };
6425
8365
  }
6426
8366
  }, {
6427
8367
  key: "nullable",
6428
8368
  value: function nullable() {
6429
- return new NumberSchema(this.options, true, [], this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
8369
+ // Explicit type args: `previewInput` would otherwise pin inference to `Src`.
8370
+ return new EnumSchema(this.values, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
6430
8371
  }
6431
8372
  }, {
6432
8373
  key: "readonly",
6433
8374
  value: function readonly() {
6434
8375
  var isReadonly = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
6435
- return new NumberSchema(this.options, this.opt, this.customValidateFunctions, isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
8376
+ return new EnumSchema(this.values, this.opt, this.customValidateFunctions, isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
6436
8377
  }
6437
8378
  }, {
6438
8379
  key: "hidden",
6439
8380
  value: function hidden() {
6440
8381
  var isHidden = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
6441
- return new NumberSchema(this.options, this.opt, this.customValidateFunctions, this.isReadonly, isHidden, this.description, this.renderInput, this.previewInput);
6442
- }
6443
- }, {
6444
- key: "max",
6445
- value: function max(_max) {
6446
- return new NumberSchema(_objectSpread2(_objectSpread2({}, this.options), {}, {
6447
- max: _max
6448
- }), this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
6449
- }
6450
- }, {
6451
- key: "min",
6452
- value: function min(_min) {
6453
- return new NumberSchema(_objectSpread2(_objectSpread2({}, this.options), {}, {
6454
- min: _min
6455
- }), this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
8382
+ return new EnumSchema(this.values, this.opt, this.customValidateFunctions, this.isReadonly, isHidden, this.description, this.renderInput, this.previewInput);
6456
8383
  }
6457
8384
  }, {
6458
8385
  key: "executeCustomValidateAt",
@@ -6468,22 +8395,34 @@ var NumberSchema = /*#__PURE__*/function (_Schema) {
6468
8395
  * instead of a preview row that navigates to it.
6469
8396
  *
6470
8397
  * Static configuration, not a callback — see `render.ts`.
8398
+ *
8399
+ * @example
8400
+ * const schema = s.array(
8401
+ * s.enum("draft", "published").render({ as: "inline" }),
8402
+ * );
8403
+ * export default c.define("/example.val.ts", schema, ["draft"]);
6471
8404
  */
6472
8405
  }, {
6473
8406
  key: "render",
6474
8407
  value: function render(input) {
6475
- return new NumberSchema(this.options, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, input, this.previewInput);
8408
+ return new EnumSchema(this.values, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, input, this.previewInput);
6476
8409
  }
6477
8410
 
6478
8411
  /**
6479
8412
  * How this VALUE is shown where a preview of it is needed — a row in a
6480
8413
  * sortable list, a reference dropdown, a search hit. Never how the field
6481
8414
  * itself is edited (that is `render`). See `preview.ts`.
8415
+ *
8416
+ * @example
8417
+ * const schema = s.array(
8418
+ * s.enum("draft", "published").preview(({ val }) => ({ title: val })),
8419
+ * );
8420
+ * export default c.define("/example.val.ts", schema, ["draft"]);
6482
8421
  */
6483
8422
  }, {
6484
8423
  key: "preview",
6485
8424
  value: function preview(select) {
6486
- return new NumberSchema(this.options, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, select);
8425
+ return new EnumSchema(this.values, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, select);
6487
8426
  }
6488
8427
  }, {
6489
8428
  key: "executePreviewItem",
@@ -6505,10 +8444,10 @@ var NumberSchema = /*#__PURE__*/function (_Schema) {
6505
8444
  value: function executeSerialize() {
6506
8445
  var _this$renderInput, _this$customValidateF;
6507
8446
  return {
6508
- type: "number",
8447
+ type: "enum",
6509
8448
  render: (_this$renderInput = this.renderInput) !== null && _this$renderInput !== void 0 ? _this$renderInput : undefined,
6510
8449
  preview: this.previewInput ? true : undefined,
6511
- options: this.options,
8450
+ values: _toConsumableArray(this.values),
6512
8451
  opt: this.opt,
6513
8452
  customValidate: this.customValidateFunctions && ((_this$customValidateF = this.customValidateFunctions) === null || _this$customValidateF === void 0 ? void 0 : _this$customValidateF.length) > 0,
6514
8453
  readonly: this.isReadonly,
@@ -6519,14 +8458,104 @@ var NumberSchema = /*#__PURE__*/function (_Schema) {
6519
8458
  }, {
6520
8459
  key: "executePreview",
6521
8460
  value: function executePreview() {
8461
+ // A leaf: nothing below it to preview.
6522
8462
  return {};
6523
8463
  }
6524
8464
  }]);
6525
8465
  }(Schema);
6526
- var number = function number(options) {
6527
- return new NumberSchema(options);
8466
+
8467
+ /**
8468
+ * Define a string that must be one of a fixed set of values.
8469
+ *
8470
+ * Named `enumSchema` rather than `enum` because `enum` is a reserved word and
8471
+ * cannot be a binding name; it is exposed as `s.enum`.
8472
+ */
8473
+ var enumSchema = function enumSchema() {
8474
+ for (var _len = arguments.length, values = new Array(_len), _key = 0; _key < _len; _key++) {
8475
+ values[_key] = arguments[_key];
8476
+ }
8477
+ return new EnumSchema(values);
6528
8478
  };
6529
8479
 
8480
+ /**
8481
+ * @deprecated Use {@link SerializedDiscriminatedUnionSchema} or
8482
+ * {@link SerializedEnumSchema}. A serialized union is now one or the other,
8483
+ * distinguished by `type` rather than by the shape of `key`.
8484
+ */
8485
+
8486
+ /** @deprecated Use {@link SerializedEnumSchema}. */
8487
+
8488
+ /** @deprecated Use {@link SerializedDiscriminatedUnionSchema}. */
8489
+
8490
+ /**
8491
+ * @deprecated There is no single union schema class any more: name
8492
+ * {@link DiscriminatedUnionSchema} or {@link EnumSchema}. The type parameters
8493
+ * are kept so existing annotations still compile, but only `Src` is used.
8494
+ */
8495
+
8496
+ /**
8497
+ * The value type of a list of literal schemas — how `s.union` typed a union of
8498
+ * literals. Inferred from `Schema<infer S>` rather than `LiteralSchema<infer S>`
8499
+ * because a `LiteralSchema` is invariant in its source (its custom validate
8500
+ * functions put it in a parameter position), so `LiteralSchema<"a">` is not
8501
+ * assignable to `LiteralSchema<"a" | "b">`.
8502
+ */
8503
+
8504
+ /**
8505
+ * @deprecated Use `s.discriminatedUnion(key, ...objects)` for a tagged union of
8506
+ * objects, or `s.enum("a", "b")` for one of a fixed set of strings.
8507
+ *
8508
+ * One thing does NOT survive the literal form: an enum's values are strings,
8509
+ * so a member literal's own `.validate()` and `.describe()` are dropped rather
8510
+ * than carried over. `s.union(s.literal("a").validate(...), ...)` ran that
8511
+ * validator as part of deciding whether the value matched; `s.enum` has no
8512
+ * member to run. A validator on a single-valued literal could only accept or
8513
+ * reject that one value, which the enum's own value list already decides — but
8514
+ * if you rely on one, keep it by validating the enum itself.
8515
+ *
8516
+ * @example
8517
+ * // was: s.union("type", s.object({ type: s.literal("a"), a: s.string() }))
8518
+ * const schema = s.discriminatedUnion(
8519
+ * "type",
8520
+ * s.object({ type: s.literal("a"), a: s.string() }),
8521
+ * );
8522
+ * export default c.define("/example.val.ts", schema, { type: "a", a: "one" });
8523
+ *
8524
+ * @example
8525
+ * // was: s.union(s.literal("a"), s.literal("b"))
8526
+ * const schema = s.enum("a", "b");
8527
+ * export default c.define("/example.val.ts", schema, "a");
8528
+ */
8529
+
8530
+ function union(keyOrFirstLiteral) {
8531
+ for (var _len = arguments.length, rest = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
8532
+ rest[_key - 1] = arguments[_key];
8533
+ }
8534
+ if (typeof keyOrFirstLiteral === "string") {
8535
+ return new DiscriminatedUnionSchema(keyOrFirstLiteral, rest);
8536
+ }
8537
+ // A literal union is an enum: the values ARE the schema, so the literals are
8538
+ // flattened into them. `key` was only ever the first member.
8539
+ var literals = [keyOrFirstLiteral].concat(rest);
8540
+ var values = [];
8541
+ var _iterator = _createForOfIteratorHelper(literals),
8542
+ _step;
8543
+ try {
8544
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
8545
+ var literal = _step.value;
8546
+ if (!(literal instanceof LiteralSchema)) {
8547
+ throw new Error("s.union: expected either a string key followed by object schemas, or literal schemas. Got: ".concat(JSON.stringify(literal)));
8548
+ }
8549
+ values.push(literal["value"]);
8550
+ }
8551
+ } catch (err) {
8552
+ _iterator.e(err);
8553
+ } finally {
8554
+ _iterator.f();
8555
+ }
8556
+ return new EnumSchema(values);
8557
+ }
8558
+
6530
8559
  var KeyOfSchema = /*#__PURE__*/function (_Schema) {
6531
8560
  function KeyOfSchema(schema, sourcePath) {
6532
8561
  var _this;
@@ -6550,12 +8579,52 @@ var KeyOfSchema = /*#__PURE__*/function (_Schema) {
6550
8579
  _this.previewInput = previewInput;
6551
8580
  return _this;
6552
8581
  }
8582
+
8583
+ /**
8584
+ * Describe this field.
8585
+ *
8586
+ * The description is shown next to the field's label in the Val editor, so
8587
+ * it is where you say what an editor needs to know but the field name cannot
8588
+ * carry. It also travels in the serialized schema, which is what the AI
8589
+ * assistant and the MCP tools read.
8590
+ *
8591
+ * Pass `null` to clear a description set earlier.
8592
+ *
8593
+ * @example
8594
+ * import authorsVal from "./authors.val"; // a record module
8595
+ * const schema = s.keyOf(authorsVal).describe("Who wrote this post");
8596
+ * export default c.define("/example.val.ts", schema, "ada");
8597
+ */
6553
8598
  _inherits(KeyOfSchema, _Schema);
6554
8599
  return _createClass(KeyOfSchema, [{
6555
8600
  key: "describe",
6556
8601
  value: function describe(description) {
6557
8602
  return new KeyOfSchema(this.schema, this.sourcePath, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.renderInput, this.previewInput);
6558
8603
  }
8604
+
8605
+ /**
8606
+ * Add a custom validation rule to this field.
8607
+ *
8608
+ * The function is called with the field's value and returns `false` when the
8609
+ * value is fine, or a STRING with the message to show when it is not. Call it
8610
+ * more than once to add more rules — they all run, and every message is
8611
+ * reported.
8612
+ *
8613
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
8614
+ * when the value is fine, and `true` is not one of the two answers.
8615
+ *
8616
+ * Validation runs in the Studio as you type, in `npx val validate` and
8617
+ * before a publish.
8618
+ *
8619
+ * @example
8620
+ * import authorsVal from "./authors.val"; // a record module
8621
+ * const schema = s
8622
+ * .keyOf(authorsVal)
8623
+ * .validate((val) =>
8624
+ * val === "placeholder" ? "Pick a real author" : false,
8625
+ * );
8626
+ * export default c.define("/example.val.ts", schema, "ada");
8627
+ */
6559
8628
  }, {
6560
8629
  key: "validate",
6561
8630
  value: function validate(validationFunction) {
@@ -6689,7 +8758,7 @@ var KeyOfSchema = /*#__PURE__*/function (_Schema) {
6689
8758
  }, {
6690
8759
  key: "nullable",
6691
8760
  value: function nullable() {
6692
- return new KeyOfSchema(this.schema, this.sourcePath, true, [], this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
8761
+ return new KeyOfSchema(this.schema, this.sourcePath, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
6693
8762
  }
6694
8763
  }, {
6695
8764
  key: "readonly",
@@ -6717,6 +8786,11 @@ var KeyOfSchema = /*#__PURE__*/function (_Schema) {
6717
8786
  * instead of a preview row that navigates to it.
6718
8787
  *
6719
8788
  * Static configuration, not a callback — see `render.ts`.
8789
+ *
8790
+ * @example
8791
+ * import authorsVal from "./authors.val"; // a record module
8792
+ * const schema = s.array(s.keyOf(authorsVal).render({ as: "inline" }));
8793
+ * export default c.define("/example.val.ts", schema, ["ada"]);
6720
8794
  */
6721
8795
  }, {
6722
8796
  key: "render",
@@ -6728,6 +8802,13 @@ var KeyOfSchema = /*#__PURE__*/function (_Schema) {
6728
8802
  * How this VALUE is shown where a preview of it is needed — a row in a
6729
8803
  * sortable list, a reference dropdown, a search hit. Never how the field
6730
8804
  * itself is edited (that is `render`). See `preview.ts`.
8805
+ *
8806
+ * @example
8807
+ * import authorsVal from "./authors.val"; // a record module
8808
+ * const schema = s.array(
8809
+ * s.keyOf(authorsVal).preview(({ val }) => ({ title: val })),
8810
+ * );
8811
+ * export default c.define("/example.val.ts", schema, ["ada"]);
6731
8812
  */
6732
8813
  }, {
6733
8814
  key: "preview",
@@ -6844,17 +8925,83 @@ var FileSchema = /*#__PURE__*/function (_Schema) {
6844
8925
  _this.previewInput = previewInput;
6845
8926
  return _this;
6846
8927
  }
8928
+
8929
+ /**
8930
+ * Describe this field.
8931
+ *
8932
+ * The description is shown next to the field's label in the Val editor, so
8933
+ * it is where you say what an editor needs to know but the field name cannot
8934
+ * carry. It also travels in the serialized schema, which is what the AI
8935
+ * assistant and the MCP tools read.
8936
+ *
8937
+ * Pass `null` to clear a description set earlier.
8938
+ *
8939
+ * @example
8940
+ * const schema = s.file().describe("The PDF offered for download");
8941
+ * export default c.define("/example.val.ts", schema, {
8942
+ * path: "/public/val/example.pdf",
8943
+ * mimeType: "application/pdf",
8944
+ * });
8945
+ */
6847
8946
  _inherits(FileSchema, _Schema);
6848
8947
  return _createClass(FileSchema, [{
6849
8948
  key: "describe",
6850
8949
  value: function describe(description) {
6851
8950
  return new FileSchema(this.options, this.opt, this.isRemote, this.customValidateFunctions, this.moduleMetadata, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.renderInput, this.previewInput);
6852
8951
  }
8952
+
8953
+ /**
8954
+ * Store the file on Val's remote content host instead of in your repository.
8955
+ *
8956
+ * The bytes still go into the patch store when the file is uploaded — the
8957
+ * push to the remote host happens at publish. What changes is where the
8958
+ * published file lives: `path` becomes a remote URL rather than a path under
8959
+ * `/public`, so the repository does not grow with every upload.
8960
+ *
8961
+ * The `path` of a remote file is a URL on the content host, and it is not
8962
+ * something to write by hand: upload the file in the Studio, or write a
8963
+ * local path and let `npx val validate --fix` upload it and rewrite the
8964
+ * `path` to the ref below.
8965
+ *
8966
+ * @example
8967
+ * const schema = s.file({ accept: "application/pdf" }).remote();
8968
+ * export default c.define("/example.val.ts", schema, {
8969
+ * path: "https://remote.val.build/file/p/my-project/b/01/v/1.0.0/h/8f2a1c/f/3b9d70/p/public/val/example.pdf",
8970
+ * mimeType: "application/pdf",
8971
+ * });
8972
+ */
6853
8973
  }, {
6854
8974
  key: "remote",
6855
8975
  value: function remote() {
6856
8976
  return new FileSchema(this.options, this.opt, true, this.customValidateFunctions, this.moduleMetadata, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
6857
8977
  }
8978
+
8979
+ /**
8980
+ * Add a custom validation rule to this field.
8981
+ *
8982
+ * The function is called with the field's value and returns `false` when the
8983
+ * value is fine, or a STRING with the message to show when it is not. Call it
8984
+ * more than once to add more rules — they all run, and every message is
8985
+ * reported.
8986
+ *
8987
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
8988
+ * when the value is fine, and `true` is not one of the two answers.
8989
+ *
8990
+ * Validation runs in the Studio as you type, in `npx val validate` and
8991
+ * before a publish.
8992
+ *
8993
+ * The second argument carries the `path` of the field being validated, for
8994
+ * when the message needs to say where the problem is.
8995
+ *
8996
+ * @example
8997
+ * const schema = s.file().validate((val) =>
8998
+ * val.path.endsWith(".pdf") ? false : "Must be a PDF",
8999
+ * );
9000
+ * export default c.define("/example.val.ts", schema, {
9001
+ * path: "/public/val/example.pdf",
9002
+ * mimeType: "application/pdf",
9003
+ * });
9004
+ */
6858
9005
  }, {
6859
9006
  key: "validate",
6860
9007
  value: function validate(validationFunction) {
@@ -7061,6 +9208,12 @@ var FileSchema = /*#__PURE__*/function (_Schema) {
7061
9208
  * instead of a preview row that navigates to it.
7062
9209
  *
7063
9210
  * Static configuration, not a callback — see `render.ts`.
9211
+ *
9212
+ * @example
9213
+ * const schema = s.array(s.file().render({ as: "inline" }));
9214
+ * export default c.define("/example.val.ts", schema, [
9215
+ * { path: "/public/val/example.pdf", mimeType: "application/pdf" },
9216
+ * ]);
7064
9217
  */
7065
9218
  }, {
7066
9219
  key: "render",
@@ -7072,6 +9225,14 @@ var FileSchema = /*#__PURE__*/function (_Schema) {
7072
9225
  * How this VALUE is shown where a preview of it is needed — a row in a
7073
9226
  * sortable list, a reference dropdown, a search hit. Never how the field
7074
9227
  * itself is edited (that is `render`). See `preview.ts`.
9228
+ *
9229
+ * @example
9230
+ * const schema = s.array(
9231
+ * s.file().preview(({ val }) => ({ title: val.path })),
9232
+ * );
9233
+ * export default c.define("/example.val.ts", schema, [
9234
+ * { path: "/public/val/example.pdf", mimeType: "application/pdf" },
9235
+ * ]);
7075
9236
  */
7076
9237
  }, {
7077
9238
  key: "preview",
@@ -7205,12 +9366,48 @@ var DateSchema = /*#__PURE__*/function (_Schema) {
7205
9366
  _this.previewInput = previewInput;
7206
9367
  return _this;
7207
9368
  }
9369
+
9370
+ /**
9371
+ * Describe this field.
9372
+ *
9373
+ * The description is shown next to the field's label in the Val editor, so
9374
+ * it is where you say what an editor needs to know but the field name cannot
9375
+ * carry. It also travels in the serialized schema, which is what the AI
9376
+ * assistant and the MCP tools read.
9377
+ *
9378
+ * Pass `null` to clear a description set earlier.
9379
+ *
9380
+ * @example
9381
+ * const schema = s.date().describe("First day the campaign is live");
9382
+ * export default c.define("/example.val.ts", schema, "2025-06-01");
9383
+ */
7208
9384
  _inherits(DateSchema, _Schema);
7209
9385
  return _createClass(DateSchema, [{
7210
9386
  key: "describe",
7211
9387
  value: function describe(description) {
7212
9388
  return new DateSchema(this.options, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.renderInput, this.previewInput);
7213
9389
  }
9390
+
9391
+ /**
9392
+ * Add a custom validation rule to this field.
9393
+ *
9394
+ * The function is called with the field's value and returns `false` when the
9395
+ * value is fine, or a STRING with the message to show when it is not. Call it
9396
+ * more than once to add more rules — they all run, and every message is
9397
+ * reported.
9398
+ *
9399
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
9400
+ * when the value is fine, and `true` is not one of the two answers.
9401
+ *
9402
+ * Validation runs in the Studio as you type, in `npx val validate` and
9403
+ * before a publish.
9404
+ *
9405
+ * @example
9406
+ * const schema = s.date().validate((val) =>
9407
+ * val >= "2025-01-01" ? false : "Must be in 2025 or later",
9408
+ * );
9409
+ * export default c.define("/example.val.ts", schema, "2025-06-01");
9410
+ */
7214
9411
  }, {
7215
9412
  key: "validate",
7216
9413
  value: function validate(validationFunction) {
@@ -7298,6 +9495,16 @@ var DateSchema = /*#__PURE__*/function (_Schema) {
7298
9495
  data: src
7299
9496
  };
7300
9497
  }
9498
+
9499
+ /**
9500
+ * Validate that the date is this date or after (inclusive).
9501
+ *
9502
+ * Written as `YYYY-MM-DD`, the same shape the value has.
9503
+ *
9504
+ * @example
9505
+ * const schema = s.date().from("2025-01-01");
9506
+ * export default c.define("/example.val.ts", schema, "2025-06-01");
9507
+ */
7301
9508
  }, {
7302
9509
  key: "from",
7303
9510
  value: function from(_from) {
@@ -7305,6 +9512,16 @@ var DateSchema = /*#__PURE__*/function (_Schema) {
7305
9512
  from: _from
7306
9513
  }), this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
7307
9514
  }
9515
+
9516
+ /**
9517
+ * Validate that the date is this date or before (inclusive).
9518
+ *
9519
+ * Written as `YYYY-MM-DD`, the same shape the value has.
9520
+ *
9521
+ * @example
9522
+ * const schema = s.date().from("2025-01-01").to("2025-12-31");
9523
+ * export default c.define("/example.val.ts", schema, "2025-06-01");
9524
+ */
7308
9525
  }, {
7309
9526
  key: "to",
7310
9527
  value: function to(_to) {
@@ -7315,7 +9532,7 @@ var DateSchema = /*#__PURE__*/function (_Schema) {
7315
9532
  }, {
7316
9533
  key: "nullable",
7317
9534
  value: function nullable() {
7318
- return new DateSchema(this.options, true, [], this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
9535
+ return new DateSchema(this.options, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
7319
9536
  }
7320
9537
  }, {
7321
9538
  key: "readonly",
@@ -7343,6 +9560,10 @@ var DateSchema = /*#__PURE__*/function (_Schema) {
7343
9560
  * instead of a preview row that navigates to it.
7344
9561
  *
7345
9562
  * Static configuration, not a callback — see `render.ts`.
9563
+ *
9564
+ * @example
9565
+ * const schema = s.array(s.date().render({ as: "inline" }));
9566
+ * export default c.define("/example.val.ts", schema, ["2025-06-01"]);
7346
9567
  */
7347
9568
  }, {
7348
9569
  key: "render",
@@ -7354,6 +9575,12 @@ var DateSchema = /*#__PURE__*/function (_Schema) {
7354
9575
  * How this VALUE is shown where a preview of it is needed — a row in a
7355
9576
  * sortable list, a reference dropdown, a search hit. Never how the field
7356
9577
  * itself is edited (that is `render`). See `preview.ts`.
9578
+ *
9579
+ * @example
9580
+ * const schema = s.array(
9581
+ * s.date().preview(({ val }) => ({ title: val })),
9582
+ * );
9583
+ * export default c.define("/example.val.ts", schema, ["2025-06-01"]);
7357
9584
  */
7358
9585
  }, {
7359
9586
  key: "preview",
@@ -7424,12 +9651,56 @@ var DateTimeSchema = /*#__PURE__*/function (_Schema) {
7424
9651
  _this.previewInput = previewInput;
7425
9652
  return _this;
7426
9653
  }
9654
+
9655
+ /**
9656
+ * Describe this field.
9657
+ *
9658
+ * The description is shown next to the field's label in the Val editor, so
9659
+ * it is where you say what an editor needs to know but the field name cannot
9660
+ * carry. It also travels in the serialized schema, which is what the AI
9661
+ * assistant and the MCP tools read.
9662
+ *
9663
+ * Pass `null` to clear a description set earlier.
9664
+ *
9665
+ * @example
9666
+ * const schema = s.datetime().describe("When the article goes live");
9667
+ * export default c.define(
9668
+ * "/example.val.ts",
9669
+ * schema,
9670
+ * "2025-06-01T09:00:00.000Z",
9671
+ * );
9672
+ */
7427
9673
  _inherits(DateTimeSchema, _Schema);
7428
9674
  return _createClass(DateTimeSchema, [{
7429
9675
  key: "describe",
7430
9676
  value: function describe(description) {
7431
9677
  return new DateTimeSchema(this.options, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.renderInput, this.previewInput);
7432
9678
  }
9679
+
9680
+ /**
9681
+ * Add a custom validation rule to this field.
9682
+ *
9683
+ * The function is called with the field's value and returns `false` when the
9684
+ * value is fine, or a STRING with the message to show when it is not. Call it
9685
+ * more than once to add more rules — they all run, and every message is
9686
+ * reported.
9687
+ *
9688
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
9689
+ * when the value is fine, and `true` is not one of the two answers.
9690
+ *
9691
+ * Validation runs in the Studio as you type, in `npx val validate` and
9692
+ * before a publish.
9693
+ *
9694
+ * @example
9695
+ * const schema = s.datetime().validate((val) =>
9696
+ * val.endsWith("Z") ? false : "Must be in UTC",
9697
+ * );
9698
+ * export default c.define(
9699
+ * "/example.val.ts",
9700
+ * schema,
9701
+ * "2025-06-01T09:00:00.000Z",
9702
+ * );
9703
+ */
7433
9704
  }, {
7434
9705
  key: "validate",
7435
9706
  value: function validate(validationFunction) {
@@ -7549,6 +9820,20 @@ var DateTimeSchema = /*#__PURE__*/function (_Schema) {
7549
9820
  data: src
7550
9821
  };
7551
9822
  }
9823
+
9824
+ /**
9825
+ * Validate that the datetime is this datetime or after (inclusive).
9826
+ *
9827
+ * Any ISO 8601 datetime string `Date.parse` understands.
9828
+ *
9829
+ * @example
9830
+ * const schema = s.datetime().from("2025-01-01T00:00:00Z");
9831
+ * export default c.define(
9832
+ * "/example.val.ts",
9833
+ * schema,
9834
+ * "2025-06-01T09:00:00.000Z",
9835
+ * );
9836
+ */
7552
9837
  }, {
7553
9838
  key: "from",
7554
9839
  value: function from(_from) {
@@ -7556,6 +9841,23 @@ var DateTimeSchema = /*#__PURE__*/function (_Schema) {
7556
9841
  from: _from
7557
9842
  }), this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
7558
9843
  }
9844
+
9845
+ /**
9846
+ * Validate that the datetime is this datetime or before (inclusive).
9847
+ *
9848
+ * Any ISO 8601 datetime string `Date.parse` understands.
9849
+ *
9850
+ * @example
9851
+ * const schema = s
9852
+ * .datetime()
9853
+ * .from("2025-01-01T00:00:00Z")
9854
+ * .to("2025-12-31T23:59:59Z");
9855
+ * export default c.define(
9856
+ * "/example.val.ts",
9857
+ * schema,
9858
+ * "2025-06-01T09:00:00.000Z",
9859
+ * );
9860
+ */
7559
9861
  }, {
7560
9862
  key: "to",
7561
9863
  value: function to(_to) {
@@ -7566,7 +9868,7 @@ var DateTimeSchema = /*#__PURE__*/function (_Schema) {
7566
9868
  }, {
7567
9869
  key: "nullable",
7568
9870
  value: function nullable() {
7569
- return new DateTimeSchema(this.options, true, [], this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
9871
+ return new DateTimeSchema(this.options, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
7570
9872
  }
7571
9873
  }, {
7572
9874
  key: "readonly",
@@ -7594,6 +9896,12 @@ var DateTimeSchema = /*#__PURE__*/function (_Schema) {
7594
9896
  * instead of a preview row that navigates to it.
7595
9897
  *
7596
9898
  * Static configuration, not a callback — see `render.ts`.
9899
+ *
9900
+ * @example
9901
+ * const schema = s.array(s.datetime().render({ as: "inline" }));
9902
+ * export default c.define("/example.val.ts", schema, [
9903
+ * "2025-06-01T09:00:00.000Z",
9904
+ * ]);
7597
9905
  */
7598
9906
  }, {
7599
9907
  key: "render",
@@ -7605,6 +9913,14 @@ var DateTimeSchema = /*#__PURE__*/function (_Schema) {
7605
9913
  * How this VALUE is shown where a preview of it is needed — a row in a
7606
9914
  * sortable list, a reference dropdown, a search hit. Never how the field
7607
9915
  * itself is edited (that is `render`). See `preview.ts`.
9916
+ *
9917
+ * @example
9918
+ * const schema = s.array(
9919
+ * s.datetime().preview(({ val }) => ({ title: val })),
9920
+ * );
9921
+ * export default c.define("/example.val.ts", schema, [
9922
+ * "2025-06-01T09:00:00.000Z",
9923
+ * ]);
7608
9924
  */
7609
9925
  }, {
7610
9926
  key: "preview",
@@ -7689,12 +10005,59 @@ var CodeSchema = /*#__PURE__*/function (_Schema) {
7689
10005
  _this.previewInput = previewInput;
7690
10006
  return _this;
7691
10007
  }
10008
+
10009
+ /**
10010
+ * Describe this field.
10011
+ *
10012
+ * The description is shown next to the field's label in the Val editor, so
10013
+ * it is where you say what an editor needs to know but the field name cannot
10014
+ * carry. It also travels in the serialized schema, which is what the AI
10015
+ * assistant and the MCP tools read.
10016
+ *
10017
+ * Pass `null` to clear a description set earlier.
10018
+ *
10019
+ * @example
10020
+ * const schema = s
10021
+ * .code({ language: "css" })
10022
+ * .describe("Injected into the page head — keep it small");
10023
+ * export default c.define(
10024
+ * "/example.val.ts",
10025
+ * schema,
10026
+ * ".hero { color: red; }",
10027
+ * );
10028
+ */
7692
10029
  _inherits(CodeSchema, _Schema);
7693
10030
  return _createClass(CodeSchema, [{
7694
10031
  key: "describe",
7695
10032
  value: function describe(description) {
7696
10033
  return new CodeSchema(this.options, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.renderInput, this.previewInput);
7697
10034
  }
10035
+
10036
+ /**
10037
+ * Add a custom validation rule to this field.
10038
+ *
10039
+ * The function is called with the field's value and returns `false` when the
10040
+ * value is fine, or a STRING with the message to show when it is not. Call it
10041
+ * more than once to add more rules — they all run, and every message is
10042
+ * reported.
10043
+ *
10044
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
10045
+ * when the value is fine, and `true` is not one of the two answers.
10046
+ *
10047
+ * Validation runs in the Studio as you type, in `npx val validate` and
10048
+ * before a publish.
10049
+ *
10050
+ * @example
10051
+ * const schema = s.code({ language: "json" }).validate((val) => {
10052
+ * try {
10053
+ * JSON.parse(val);
10054
+ * return false;
10055
+ * } catch {
10056
+ * return "Must be valid JSON";
10057
+ * }
10058
+ * });
10059
+ * export default c.define("/example.val.ts", schema, '{ "a": 1 }');
10060
+ */
7698
10061
  }, {
7699
10062
  key: "validate",
7700
10063
  value: function validate(validationFunction) {
@@ -7753,7 +10116,7 @@ var CodeSchema = /*#__PURE__*/function (_Schema) {
7753
10116
  }, {
7754
10117
  key: "nullable",
7755
10118
  value: function nullable() {
7756
- return new CodeSchema(this.options, true, [], this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
10119
+ return new CodeSchema(this.options, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
7757
10120
  }
7758
10121
  }, {
7759
10122
  key: "readonly",
@@ -7774,6 +10137,14 @@ var CodeSchema = /*#__PURE__*/function (_Schema) {
7774
10137
  * instead of a preview row that navigates to it.
7775
10138
  *
7776
10139
  * Static configuration, not a callback — see `render.ts`.
10140
+ *
10141
+ * @example
10142
+ * const schema = s.array(
10143
+ * s.code({ language: "css" }).render({ as: "inline" }),
10144
+ * );
10145
+ * export default c.define("/example.val.ts", schema, [
10146
+ * ".hero { color: red; }",
10147
+ * ]);
7777
10148
  */
7778
10149
  }, {
7779
10150
  key: "render",
@@ -7785,6 +10156,16 @@ var CodeSchema = /*#__PURE__*/function (_Schema) {
7785
10156
  * How this VALUE is shown where a preview of it is needed — a row in a
7786
10157
  * sortable list, a reference dropdown, a search hit. Never how the field
7787
10158
  * itself is edited (that is `render`). See `preview.ts`.
10159
+ *
10160
+ * @example
10161
+ * const schema = s.array(
10162
+ * s.code({ language: "css" }).preview(({ val }) => ({
10163
+ * title: val.split("\n")[0],
10164
+ * })),
10165
+ * );
10166
+ * export default c.define("/example.val.ts", schema, [
10167
+ * ".hero { color: red; }",
10168
+ * ]);
7788
10169
  */
7789
10170
  }, {
7790
10171
  key: "preview",
@@ -8335,12 +10716,54 @@ var ColorSchema = /*#__PURE__*/function (_Schema) {
8335
10716
  _this.previewInput = previewInput;
8336
10717
  return _this;
8337
10718
  }
10719
+
10720
+ /**
10721
+ * Describe this field.
10722
+ *
10723
+ * The description is shown next to the field's label in the Val editor, so
10724
+ * it is where you say what an editor needs to know but the field name cannot
10725
+ * carry. It also travels in the serialized schema, which is what the AI
10726
+ * assistant and the MCP tools read.
10727
+ *
10728
+ * Pass `null` to clear a description set earlier.
10729
+ *
10730
+ * @example
10731
+ * const schema = s.color().describe("Background of the hero section");
10732
+ * export default c.define(
10733
+ * "/example.val.ts",
10734
+ * schema,
10735
+ * "hsl(217.22 91.22% 59.8%)",
10736
+ * );
10737
+ */
8338
10738
  _inherits(ColorSchema, _Schema);
8339
10739
  return _createClass(ColorSchema, [{
8340
10740
  key: "describe",
8341
10741
  value: function describe(description) {
8342
10742
  return new ColorSchema(this.options, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.renderInput, this.previewInput);
8343
10743
  }
10744
+
10745
+ /**
10746
+ * Add a custom validation rule to this field.
10747
+ *
10748
+ * The function is called with the field's value and returns `false` when the
10749
+ * value is fine, or a STRING with the message to show when it is not. Call it
10750
+ * more than once to add more rules — they all run, and every message is
10751
+ * reported.
10752
+ *
10753
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
10754
+ * when the value is fine, and `true` is not one of the two answers.
10755
+ *
10756
+ * Validation runs in the Studio as you type, in `npx val validate` and
10757
+ * before a publish.
10758
+ *
10759
+ * @example
10760
+ * const schema = s
10761
+ * .color({ format: "hex" })
10762
+ * .validate((val) =>
10763
+ * val === val.toLowerCase() ? false : "Write hex in lower case",
10764
+ * );
10765
+ * export default c.define("/example.val.ts", schema, "#3b82f6");
10766
+ */
8344
10767
  }, {
8345
10768
  key: "validate",
8346
10769
  value: function validate(validationFunction) {
@@ -8441,7 +10864,7 @@ var ColorSchema = /*#__PURE__*/function (_Schema) {
8441
10864
  }, {
8442
10865
  key: "nullable",
8443
10866
  value: function nullable() {
8444
- return new ColorSchema(this.options, true, [], this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
10867
+ return new ColorSchema(this.options, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
8445
10868
  }
8446
10869
  }, {
8447
10870
  key: "readonly",
@@ -8462,6 +10885,12 @@ var ColorSchema = /*#__PURE__*/function (_Schema) {
8462
10885
  * instead of a preview row that navigates to it.
8463
10886
  *
8464
10887
  * Static configuration, not a callback — see `render.ts`.
10888
+ *
10889
+ * @example
10890
+ * const schema = s.array(s.color().render({ as: "inline" }));
10891
+ * export default c.define("/example.val.ts", schema, [
10892
+ * "hsl(217.22 91.22% 59.8%)",
10893
+ * ]);
8465
10894
  */
8466
10895
  }, {
8467
10896
  key: "render",
@@ -8473,6 +10902,14 @@ var ColorSchema = /*#__PURE__*/function (_Schema) {
8473
10902
  * How this VALUE is shown where a preview of it is needed — a row in a
8474
10903
  * sortable list, a reference dropdown, a search hit. Never how the field
8475
10904
  * itself is edited (that is `render`). See `preview.ts`.
10905
+ *
10906
+ * @example
10907
+ * const schema = s.array(
10908
+ * s.color().preview(({ val }) => ({ title: val })),
10909
+ * );
10910
+ * export default c.define("/example.val.ts", schema, [
10911
+ * "hsl(217.22 91.22% 59.8%)",
10912
+ * ]);
8476
10913
  */
8477
10914
  }, {
8478
10915
  key: "preview",
@@ -8601,6 +11038,8 @@ function initSchema() {
8601
11038
  array: array,
8602
11039
  object: object,
8603
11040
  number: number,
11041
+ discriminatedUnion: discriminatedUnion,
11042
+ "enum": enumSchema,
8604
11043
  union: union,
8605
11044
  // oneOf,
8606
11045
  richtext: richtext,
@@ -8614,6 +11053,7 @@ function initSchema() {
8614
11053
  datetime: datetime,
8615
11054
  color: color,
8616
11055
  code: code,
11056
+ locale: locale,
8617
11057
  route: route,
8618
11058
  router: router,
8619
11059
  images: images,
@@ -8752,6 +11192,8 @@ var Hash = /*#__PURE__*/function () {
8752
11192
  _defineProperty(this, "F", 0x9b05688c | 0);
8753
11193
  _defineProperty(this, "G", 0x1f83d9ab | 0);
8754
11194
  _defineProperty(this, "H", 0x5be0cd19 | 0);
11195
+ _defineProperty(this, "_byte", void 0);
11196
+ _defineProperty(this, "_word", void 0);
8755
11197
  _defineProperty(this, "_size", 0);
8756
11198
  _defineProperty(this, "_sp", 0);
8757
11199
  if (!sharedBuffer || sharedOffset >= N.allocTotal) {
@@ -9519,32 +11961,26 @@ function hasRemoteFileSchema(schema) {
9519
11961
  }
9520
11962
  }
9521
11963
  return false;
9522
- } else if (schema.type === "union") {
9523
- var unionStringSchema = _typeof(schema.key) === "object" && schema.key.type === "literal" ? schema : undefined;
9524
- var unionObjectSchema = typeof schema.key === "string" ? schema : undefined;
9525
- if (unionStringSchema) {
9526
- // A string union's items are literals, so there is nothing to look inside.
9527
- return false;
9528
- }
9529
- if (unionObjectSchema) {
9530
- var _iterator = _createForOfIteratorHelper(unionObjectSchema.items),
9531
- _step;
9532
- try {
9533
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
9534
- var item = _step.value;
9535
- var _hasRemoteFile = hasRemoteFileSchema(item);
9536
- if (_hasRemoteFile) {
9537
- return true;
9538
- }
11964
+ } else if (schema.type === "discriminated-union") {
11965
+ var _iterator = _createForOfIteratorHelper(schema.items),
11966
+ _step;
11967
+ try {
11968
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
11969
+ var item = _step.value;
11970
+ var _hasRemoteFile = hasRemoteFileSchema(item);
11971
+ if (_hasRemoteFile) {
11972
+ return true;
9539
11973
  }
9540
- } catch (err) {
9541
- _iterator.e(err);
9542
- } finally {
9543
- _iterator.f();
9544
11974
  }
11975
+ } catch (err) {
11976
+ _iterator.e(err);
11977
+ } finally {
11978
+ _iterator.f();
9545
11979
  }
9546
11980
  return false;
9547
- } else if (schema.type === "boolean" || schema.type === "number" || schema.type === "string" || schema.type === "literal" || schema.type === "date" || schema.type === "dateTime" || schema.type === "color" || schema.type === "code" || schema.type === "keyOf" || schema.type === "route") {
11981
+ } else if (schema.type === "boolean" ||
11982
+ // An enum's values are strings, so there is nothing to look inside.
11983
+ schema.type === "enum" || schema.type === "number" || schema.type === "string" || schema.type === "literal" || schema.type === "date" || schema.type === "dateTime" || schema.type === "color" || schema.type === "code" || schema.type === "keyOf" || schema.type === "route" || schema.type === "locale") {
9548
11984
  return false;
9549
11985
  } else {
9550
11986
  var exhaustiveCheck = schema;
@@ -9598,27 +12034,25 @@ function hasMediaSchema(schema) {
9598
12034
  }
9599
12035
  }
9600
12036
  return false;
9601
- } else if (schema.type === "union") {
9602
- var unionObjectSchema = typeof schema.key === "string" ? schema : undefined;
9603
- if (unionObjectSchema) {
9604
- var _iterator2 = _createForOfIteratorHelper(unionObjectSchema.items),
9605
- _step2;
9606
- try {
9607
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
9608
- var item = _step2.value;
9609
- if (hasMediaSchema(item)) {
9610
- return true;
9611
- }
12037
+ } else if (schema.type === "discriminated-union") {
12038
+ var _iterator2 = _createForOfIteratorHelper(schema.items),
12039
+ _step2;
12040
+ try {
12041
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
12042
+ var item = _step2.value;
12043
+ if (hasMediaSchema(item)) {
12044
+ return true;
9612
12045
  }
9613
- } catch (err) {
9614
- _iterator2.e(err);
9615
- } finally {
9616
- _iterator2.f();
9617
12046
  }
12047
+ } catch (err) {
12048
+ _iterator2.e(err);
12049
+ } finally {
12050
+ _iterator2.f();
9618
12051
  }
9619
- // A string union's items are literals, so there is nothing to look inside.
9620
12052
  return false;
9621
- } else if (schema.type === "boolean" || schema.type === "number" || schema.type === "string" || schema.type === "literal" || schema.type === "date" || schema.type === "dateTime" || schema.type === "color" || schema.type === "code" || schema.type === "keyOf" || schema.type === "route" ||
12053
+ } else if (schema.type === "boolean" ||
12054
+ // An enum's values are strings, so there is nothing to look inside.
12055
+ schema.type === "enum" || schema.type === "number" || schema.type === "string" || schema.type === "literal" || schema.type === "date" || schema.type === "dateTime" || schema.type === "color" || schema.type === "code" || schema.type === "keyOf" || schema.type === "route" || schema.type === "locale" ||
9622
12056
  // A settings module holds the assistant's configuration, not content, and
9623
12057
  // nothing in it is media.
9624
12058
  schema.type === "settings") {
@@ -9897,6 +12331,46 @@ function getFileHash(text) {
9897
12331
  return hashToRemoteFileHash(getSHA256Hash(new Uint8Array(text)));
9898
12332
  }
9899
12333
 
12334
+ /**
12335
+ * The variant of a discriminated union that a value takes, by its tag.
12336
+ *
12337
+ * A discriminated union is a fork rather than a level: the variant IS the node,
12338
+ * and everything a caller wants to know about that node — its fields, its
12339
+ * `locale` field, the schema to draw it with — is on the variant and not on the
12340
+ * union. Resolving it takes the source, so it cannot be done from the schema
12341
+ * alone, which is why every caller that has only the union ends up needing this.
12342
+ *
12343
+ * `undefined` where the tag matches nothing, rather than a fallback to the first
12344
+ * variant: a value whose tag names no variant is not a member of the union,
12345
+ * validation is already saying so, and reading fields out of the wrong shape
12346
+ * would answer confidently and wrongly.
12347
+ *
12348
+ * `s.enum()` never reaches here — it is a leaf whose values are strings, with no
12349
+ * variants to resolve — so anything that is not a discriminated union answers
12350
+ * `undefined` too.
12351
+ */
12352
+ function discriminatedUnionBranchOf(schema, tag) {
12353
+ if ((schema === null || schema === void 0 ? void 0 : schema.type) !== "discriminated-union") {
12354
+ return undefined;
12355
+ }
12356
+ var _iterator = _createForOfIteratorHelper(schema.items),
12357
+ _step;
12358
+ try {
12359
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
12360
+ var item = _step.value;
12361
+ var discriminator = item.items[schema.key];
12362
+ if ((discriminator === null || discriminator === void 0 ? void 0 : discriminator.type) === "literal" && discriminator.value === tag) {
12363
+ return item;
12364
+ }
12365
+ }
12366
+ } catch (err) {
12367
+ _iterator.e(err);
12368
+ } finally {
12369
+ _iterator.f();
12370
+ }
12371
+ return undefined;
12372
+ }
12373
+
9900
12374
  function deserializeSchema(serialized) {
9901
12375
  var schema = deserializeSchemaImpl(serialized);
9902
12376
  if (serialized.readonly) {
@@ -9908,7 +12382,7 @@ function deserializeSchema(serialized) {
9908
12382
  return schema;
9909
12383
  }
9910
12384
  function deserializeSchemaImpl(serialized) {
9911
- var _serialized$options, _serialized$options2, _serialized$render, _serialized$multiline, _serialized$render2, _serialized$render3, _serialized$render4, _serialized$render5, _serialized$render6, _serialized$render7, _serialized$accept, _serialized$directory, _serialized$remote, _serialized$jsonValue, _serialized$render9, _serialized$external, _serialized$render0, _serialized$render10, _serialized$render11, _serialized$render12, _serialized$render13, _serialized$render14, _serialized$render15;
12385
+ var _serialized$options, _serialized$options2, _serialized$render, _serialized$multiline, _serialized$render2, _serialized$render3, _serialized$render4, _serialized$render5, _serialized$render6, _serialized$render7, _serialized$render8, _serialized$accept, _serialized$directory, _serialized$remote, _serialized$jsonValue, _serialized$render0, _serialized$external, _serialized$render1, _serialized$render12, _serialized$render13, _serialized$render14, _serialized$render15, _serialized$render16, _serialized$render17;
9912
12386
  switch (serialized.type) {
9913
12387
  case "string":
9914
12388
  return new StringSchema(_objectSpread2(_objectSpread2({}, serialized.options), {}, {
@@ -9937,28 +12411,32 @@ function deserializeSchemaImpl(serialized) {
9937
12411
  key = _ref4[0],
9938
12412
  item = _ref4[1];
9939
12413
  return [key, deserializeSchema(item)];
9940
- })), serialized.opt, false, false, serialized.description);
12414
+ })), serialized.opt, false, false, serialized.description,
12415
+ // Carried through, or the Studio's validation worker — which only ever
12416
+ // sees the deserialized schema — would run every field's own rules and
12417
+ // none of the section's, so a language declared twice would be reported
12418
+ // by the CLI and silently accepted in the Studio.
12419
+ serialized.section);
9941
12420
  case "array":
9942
12421
  return new ArraySchema(deserializeSchema(serialized.item), serialized.opt, [], false, false, serialized.description,
9943
12422
  // The preview closure cannot survive serialization; the render can.
9944
12423
  null, (_serialized$render6 = serialized.render) !== null && _serialized$render6 !== void 0 ? _serialized$render6 : null);
9945
- case "union":
9946
- return new UnionSchema(typeof serialized.key === "string" ? serialized.key :
9947
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
9948
- deserializeSchema(serialized.key),
9949
- // TODO: we do not really need any here - right?
12424
+ case "discriminated-union":
12425
+ return new DiscriminatedUnionSchema(serialized.key,
9950
12426
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
9951
12427
  serialized.items.map(deserializeSchema),
9952
12428
  // TODO: we do not really need any here - right?
9953
12429
  serialized.opt, [], false, false, serialized.description, (_serialized$render7 = serialized.render) !== null && _serialized$render7 !== void 0 ? _serialized$render7 : null);
12430
+ case "enum":
12431
+ return new EnumSchema(serialized.values, serialized.opt, [], false, false, serialized.description, (_serialized$render8 = serialized.render) !== null && _serialized$render8 !== void 0 ? _serialized$render8 : null);
9954
12432
  case "richtext":
9955
12433
  {
9956
- var _serialized$options3, _serialized$options4, _serialized$options5, _serialized$options6, _serialized$render8;
12434
+ var _serialized$options3, _serialized$options4, _serialized$options5, _serialized$options6, _serialized$render9;
9957
12435
  var deserializedOptions = _objectSpread2(_objectSpread2({}, serialized.options || {}), {}, {
9958
12436
  a: _typeof((_serialized$options3 = serialized.options) === null || _serialized$options3 === void 0 ? void 0 : _serialized$options3.a) === "object" ? deserializeSchema(serialized.options.a) : (_serialized$options4 = serialized.options) === null || _serialized$options4 === void 0 ? void 0 : _serialized$options4.a,
9959
12437
  img: _typeof((_serialized$options5 = serialized.options) === null || _serialized$options5 === void 0 ? void 0 : _serialized$options5.img) === "object" ? deserializeSchema(serialized.options.img) : (_serialized$options6 = serialized.options) === null || _serialized$options6 === void 0 ? void 0 : _serialized$options6.img
9960
12438
  });
9961
- return new RichTextSchema(deserializedOptions, serialized.opt, [], false, false, serialized.description, (_serialized$render8 = serialized.render) !== null && _serialized$render8 !== void 0 ? _serialized$render8 : null);
12439
+ return new RichTextSchema(deserializedOptions, serialized.opt, [], false, false, serialized.description, (_serialized$render9 = serialized.render) !== null && _serialized$render9 !== void 0 ? _serialized$render9 : null);
9962
12440
  }
9963
12441
  case "record":
9964
12442
  return new RecordSchema(deserializeSchema(serialized.item), serialized.opt, [], null, serialized.key ? deserializeSchema(serialized.key) : null, serialized.mediaType ? {
@@ -9969,33 +12447,38 @@ function deserializeSchemaImpl(serialized) {
9969
12447
  altSchema: serialized.alt ? deserializeSchema(serialized.alt) : undefined
9970
12448
  } : undefined, false, false, serialized.description, (_serialized$jsonValue = serialized.jsonValues) !== null && _serialized$jsonValue !== void 0 ? _serialized$jsonValue : false,
9971
12449
  // The preview closure cannot survive serialization; the render can.
9972
- null, (_serialized$render9 = serialized.render) !== null && _serialized$render9 !== void 0 ? _serialized$render9 : null, // The external label is static data, so it survives serialization and
12450
+ null, (_serialized$render0 = serialized.render) !== null && _serialized$render0 !== void 0 ? _serialized$render0 : null, // The external label is static data, so it survives serialization and
9973
12451
  // must be carried back: a deserialized schema that forgot it would walk
9974
12452
  // an external record as if its entries were local.
9975
12453
  (_serialized$external = serialized.external) !== null && _serialized$external !== void 0 ? _serialized$external : null);
9976
12454
  case "keyOf":
9977
- return new KeyOfSchema(serialized.schema, serialized.path, serialized.opt, [], false, false, serialized.description, (_serialized$render0 = serialized.render) !== null && _serialized$render0 !== void 0 ? _serialized$render0 : null);
12455
+ return new KeyOfSchema(serialized.schema, serialized.path, serialized.opt, [], false, false, serialized.description, (_serialized$render1 = serialized.render) !== null && _serialized$render1 !== void 0 ? _serialized$render1 : null);
12456
+ case "locale":
12457
+ {
12458
+ var _serialized$render10;
12459
+ return new LocaleSchema(serialized.opt, [], false, false, serialized.description, (_serialized$render10 = serialized.render) !== null && _serialized$render10 !== void 0 ? _serialized$render10 : null);
12460
+ }
9978
12461
  case "route":
9979
12462
  {
9980
- var _serialized$render1;
12463
+ var _serialized$render11;
9981
12464
  var routeOptions = serialized.options ? {
9982
12465
  include: serialized.options.include ? new RegExp(serialized.options.include.source, serialized.options.include.flags) : undefined,
9983
12466
  exclude: serialized.options.exclude ? new RegExp(serialized.options.exclude.source, serialized.options.exclude.flags) : undefined
9984
12467
  } : undefined;
9985
- return new RouteSchema(routeOptions, serialized.opt, [], false, false, serialized.description, (_serialized$render1 = serialized.render) !== null && _serialized$render1 !== void 0 ? _serialized$render1 : null);
12468
+ return new RouteSchema(routeOptions, serialized.opt, [], false, false, serialized.description, (_serialized$render11 = serialized.render) !== null && _serialized$render11 !== void 0 ? _serialized$render11 : null);
9986
12469
  }
9987
12470
  case "file":
9988
- return new FileSchema(serialized.options, serialized.opt, serialized.remote, [], {}, false, false, serialized.description, (_serialized$render10 = serialized.render) !== null && _serialized$render10 !== void 0 ? _serialized$render10 : null);
12471
+ return new FileSchema(serialized.options, serialized.opt, serialized.remote, [], {}, false, false, serialized.description, (_serialized$render12 = serialized.render) !== null && _serialized$render12 !== void 0 ? _serialized$render12 : null);
9989
12472
  case "image":
9990
- return new ImageSchema(serialized.options, serialized.opt, serialized.remote, [], {}, false, false, serialized.description, (_serialized$render11 = serialized.render) !== null && _serialized$render11 !== void 0 ? _serialized$render11 : null);
12473
+ return new ImageSchema(serialized.options, serialized.opt, serialized.remote, [], {}, false, false, serialized.description, (_serialized$render13 = serialized.render) !== null && _serialized$render13 !== void 0 ? _serialized$render13 : null);
9991
12474
  case "date":
9992
- return new DateSchema(serialized.options, serialized.opt, [], false, false, serialized.description, (_serialized$render12 = serialized.render) !== null && _serialized$render12 !== void 0 ? _serialized$render12 : null);
12475
+ return new DateSchema(serialized.options, serialized.opt, [], false, false, serialized.description, (_serialized$render14 = serialized.render) !== null && _serialized$render14 !== void 0 ? _serialized$render14 : null);
9993
12476
  case "dateTime":
9994
- return new DateTimeSchema(serialized.options, serialized.opt, [], false, false, serialized.description, (_serialized$render13 = serialized.render) !== null && _serialized$render13 !== void 0 ? _serialized$render13 : null);
12477
+ return new DateTimeSchema(serialized.options, serialized.opt, [], false, false, serialized.description, (_serialized$render15 = serialized.render) !== null && _serialized$render15 !== void 0 ? _serialized$render15 : null);
9995
12478
  case "color":
9996
- return new ColorSchema(serialized.options, serialized.opt, [], false, false, serialized.description, (_serialized$render14 = serialized.render) !== null && _serialized$render14 !== void 0 ? _serialized$render14 : null);
12479
+ return new ColorSchema(serialized.options, serialized.opt, [], false, false, serialized.description, (_serialized$render16 = serialized.render) !== null && _serialized$render16 !== void 0 ? _serialized$render16 : null);
9997
12480
  case "code":
9998
- return new CodeSchema(serialized.options, serialized.opt, [], false, false, serialized.description, (_serialized$render15 = serialized.render) !== null && _serialized$render15 !== void 0 ? _serialized$render15 : null);
12481
+ return new CodeSchema(serialized.options, serialized.opt, [], false, false, serialized.description, (_serialized$render17 = serialized.render) !== null && _serialized$render17 !== void 0 ? _serialized$render17 : null);
9999
12482
  default:
10000
12483
  {
10001
12484
  var exhaustiveCheck = serialized;
@@ -10204,10 +12687,11 @@ function previewScope(paths) {
10204
12687
  * question, and a disagreement between them is a row you can edit in place but
10205
12688
  * that "add" navigates away from.
10206
12689
  *
10207
- * A tagged union counts as inline when the union itself declares it OR when
10208
- * ANY of its variants does. A page-builder list is `s.array(s.union("type",
10209
- * block, block, ...))` and the natural place to write the render is on the
10210
- * blocks, one per block type — the union is a dispatch, not something the
12690
+ * A discriminated union counts as inline when the union itself declares it OR
12691
+ * when ANY of its variants does. A page-builder list is
12692
+ * `s.array(s.discriminatedUnion("type", block, block, ...))` and the natural
12693
+ * place to write the render is on the blocks, one per block type — the union
12694
+ * is a dispatch, not something the
10211
12695
  * author thinks of as the field. `some` rather than `every` because the row
10212
12696
  * draws the union's own editor (the tag selector plus the matched variant's
10213
12697
  * fields), which handles every variant either way: with `every`, adding one
@@ -10224,7 +12708,7 @@ function isInlineRender(schema) {
10224
12708
  if (((_schema$render = schema.render) === null || _schema$render === void 0 ? void 0 : _schema$render.as) === "inline") {
10225
12709
  return true;
10226
12710
  }
10227
- if (schema.type === "union") {
12711
+ if (schema.type === "discriminated-union") {
10228
12712
  return schema.items.some(function (item) {
10229
12713
  var _item$render;
10230
12714
  return ((_item$render = item.render) === null || _item$render === void 0 ? void 0 : _item$render.as) === "inline";
@@ -10661,7 +13145,7 @@ function isPageRouter(routerId) {
10661
13145
 
10662
13146
  var corePackageJson = {
10663
13147
  name: "@valbuild/core",
10664
- version: "0.125.0",
13148
+ version: "0.127.0",
10665
13149
  "private": false,
10666
13150
  description: "Val - supercharged hard-coded content",
10667
13151
  repository: {
@@ -10832,4 +13316,4 @@ function tryJsonParse(str) {
10832
13316
  }
10833
13317
  }
10834
13318
 
10835
- export { parseTanStackRoutePattern as $, ArraySchema as A, BooleanSchema as B, ColorSchema as C, DEFAULT_CONTENT_HOST as D, COLOR_FORMATS as E, FATAL_ERROR_TYPES as F, GenericSelector as G, DEFAULT_COLOR_FORMAT as H, Internal as I, DateTimeSchema as J, KeyOfSchema as K, RouteSchema as L, ModuleFilePathSep as M, NumberSchema as N, ObjectSchema as O, PatchError as P, RichTextSchema as Q, RecordSchema as R, Schema as S, LiteralSchema as T, UnionSchema as U, VAL_EXTENSION as V, deserializeSchema as W, previewScope as X, isInlineRender as Y, parseNextJsRoutePattern as Z, _typeof as _, _slicedToArray as a, tanStackSegmentsOfRoutePath as a0, validateUrlAgainstPattern as a1, getSourcePathFromRoute as a2, isPageRouter as a3, _createClass as b, _classCallCheck as c, _toConsumableArray as d, DEFAULT_APP_HOST as e, extractValModules as f, computeValModuleShas as g, hasRemoteFileSchema as h, initVal as i, hasMediaSchema as j, DEFAULT_VAL_REMOTE_HOST as k, derefPatch as l, modules as m, StringSchema as n, ImageSchema as o, FileSchema as p, DateSchema as q, SettingsSchema as r, splitModuleFilePathAndModulePath as s, ASSISTANT_SETTINGS_MAX_LENGTH as t, assistantAvailability as u, SETTINGS_MODULE_CONVENTION as v, isRootModuleFilePath as w, resolveSettingsModule as x, CodeSchema as y, CODE_LANGUAGES as z };
13319
+ export { DiscriminatedUnionSchema as $, ArraySchema as A, BooleanSchema as B, declaredKeySetOf as C, DEFAULT_CONTENT_HOST as D, missingDeclaredKeys as E, FATAL_ERROR_TYPES as F, GenericSelector as G, discriminatedUnionBranchOf as H, Internal as I, ColorSchema as J, CodeSchema as K, LocaleSchema as L, ModuleFilePathSep as M, NumberSchema as N, ObjectSchema as O, PatchError as P, CODE_LANGUAGES as Q, RecordSchema as R, Schema as S, COLOR_FORMATS as T, DEFAULT_COLOR_FORMAT as U, VAL_EXTENSION as V, DateTimeSchema as W, KeyOfSchema as X, RouteSchema as Y, RichTextSchema as Z, _typeof as _, _slicedToArray as a, EnumSchema as a0, LiteralSchema as a1, deserializeSchema as a2, previewScope as a3, isInlineRender as a4, parseNextJsRoutePattern as a5, parseTanStackRoutePattern as a6, tanStackSegmentsOfRoutePath as a7, validateUrlAgainstPattern as a8, getSourcePathFromRoute as a9, isPageRouter as aa, _createClass as b, _classCallCheck as c, _toConsumableArray as d, DEFAULT_APP_HOST as e, extractValModules as f, computeValModuleShas as g, hasRemoteFileSchema as h, initVal as i, hasMediaSchema as j, DEFAULT_VAL_REMOTE_HOST as k, derefPatch as l, modules as m, StringSchema as n, ImageSchema as o, FileSchema as p, DateSchema as q, SettingsSchema as r, splitModuleFilePathAndModulePath as s, ASSISTANT_SETTINGS_MAX_LENGTH as t, assistantAvailability as u, SETTINGS_MODULE_CONVENTION as v, isRootModuleFilePath as w, resolveSettingsModule as x, localeTagError as y, localeOfValue as z };