@valbuild/core 0.126.0 → 0.128.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 (32) hide show
  1. package/CHANGELOG.md +296 -0
  2. package/dist/declarations/src/index.d.ts +5 -1
  3. package/dist/declarations/src/initSchema.d.ts +31 -15
  4. package/dist/declarations/src/locale.d.ts +55 -0
  5. package/dist/declarations/src/schema/array.d.ts +4 -0
  6. package/dist/declarations/src/schema/declaredKeys.d.ts +37 -0
  7. package/dist/declarations/src/schema/discriminatedUnion.d.ts +4 -0
  8. package/dist/declarations/src/schema/discriminatedUnionBranch.d.ts +20 -0
  9. package/dist/declarations/src/schema/enum.d.ts +9 -0
  10. package/dist/declarations/src/schema/file.d.ts +3 -3
  11. package/dist/declarations/src/schema/{files.d.ts → fileset.d.ts} +10 -10
  12. package/dist/declarations/src/schema/hasRemoteFileSchema.d.ts +3 -3
  13. package/dist/declarations/src/schema/image.d.ts +9 -5
  14. package/dist/declarations/src/schema/{images.d.ts → imageset.d.ts} +54 -21
  15. package/dist/declarations/src/schema/index.d.ts +116 -3
  16. package/dist/declarations/src/schema/literal.d.ts +2 -0
  17. package/dist/declarations/src/schema/locale.d.ts +132 -0
  18. package/dist/declarations/src/schema/object.d.ts +7 -0
  19. package/dist/declarations/src/schema/record.d.ts +57 -7
  20. package/dist/declarations/src/schema/settings.d.ts +54 -1
  21. package/dist/declarations/src/schema/validation/ValidationFix.d.ts +1 -1
  22. package/dist/declarations/src/source/settings.d.ts +51 -0
  23. package/dist/{index-6db78f3c.cjs.prod.js → index-053ccde2.cjs.prod.js} +1114 -91
  24. package/dist/{index-b41b638f.cjs.dev.js → index-c3ae637d.cjs.dev.js} +1114 -91
  25. package/dist/{index-cd25c2dc.esm.js → index-db41de3c.esm.js} +1109 -92
  26. package/dist/valbuild-core.cjs.dev.js +7 -1
  27. package/dist/valbuild-core.cjs.prod.js +7 -1
  28. package/dist/valbuild-core.esm.js +1 -1
  29. package/package.json +1 -1
  30. package/patch/dist/valbuild-core-patch.cjs.dev.js +1 -1
  31. package/patch/dist/valbuild-core-patch.cjs.prod.js +1 -1
  32. package/patch/dist/valbuild-core-patch.esm.js +2 -2
@@ -163,19 +163,217 @@ function _inherits(t, e) {
163
163
  // import { SerializedI18nSchema } from "./future/i18n";
164
164
  // import { SerializedOneOfSchema } from "./future/oneOf";
165
165
 
166
+ /**
167
+ * A locale scope's path, as something to read in a message.
168
+ *
169
+ * The walk names the item of an array or record `*`, which says nothing to
170
+ * whoever has to fix this. `[]` is the convention people already read as "each
171
+ * of these", and a path that is nothing BUT items has no field to name at all.
172
+ */
173
+ function describeScopePath(path) {
174
+ if (path.every(function (segment) {
175
+ return segment === "*";
176
+ })) {
177
+ return "its entries";
178
+ }
179
+ var rendered = "";
180
+ var _iterator = result._createForOfIteratorHelper(path),
181
+ _step;
182
+ try {
183
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
184
+ var segment = _step.value;
185
+ if (segment === "*") {
186
+ rendered += "[]";
187
+ } else {
188
+ rendered += rendered === "" ? segment : ".".concat(segment);
189
+ }
190
+ }
191
+ } catch (err) {
192
+ _iterator.e(err);
193
+ } finally {
194
+ _iterator.f();
195
+ }
196
+ return "'".concat(rendered, "'");
197
+ }
166
198
  var Schema = /*#__PURE__*/function () {
167
199
  function Schema() {
168
200
  _classCallCheck(this, Schema);
201
+ /**
202
+ * The answer, computed once per schema.
203
+ *
204
+ * Memoised because the question is asked from `executeValidate`, which runs
205
+ * once per SOURCE instance: an array of five hundred blocks asks the item
206
+ * schema five hundred times, and each answer walks the schema below it
207
+ * looking for a nested scope. The answer cannot differ between two instances
208
+ * of the same schema — nothing here reads the source — so the walk is done
209
+ * once and the result handed back after that.
210
+ *
211
+ * The error is still reported at every instance's path. Val's validation
212
+ * errors are keyed by source path and that is where the Studio draws them, so
213
+ * a schema error on a row is a schema error on every row; reporting it on one
214
+ * arbitrary row would put it somewhere nobody was looking.
215
+ */
216
+ _defineProperty(this, "localeScopeErrorsMemo", void 0);
169
217
  }
170
218
  return _createClass(Schema, [{
219
+ key: "isLocaleField",
220
+ value:
221
+ /**
222
+ * Whether this node is a locale — the field that MARKS a scope, `s.locale()`.
223
+ *
224
+ * Only `LocaleSchema` overrides this. A base implementation rather than an
225
+ * abstract one on purpose: every other schema class answers no, and a
226
+ * question every class had to answer would be twenty edits for one yes.
227
+ *
228
+ * Not the same question as {@link opensLocaleScope}: a locale field marks the
229
+ * scope, the object AROUND it is the scope. See `localeScope.ts`.
230
+ */
231
+ function isLocaleField() {
232
+ return false;
233
+ }
234
+
235
+ /**
236
+ * Whether this node OPENS a locale scope, and what opens it.
237
+ *
238
+ * An object with a `s.locale()` field opens one; so does a record keyed by
239
+ * `s.locale()`. Everything below such a node is in that one language, which
240
+ * is why a scope may not contain another — see `localeScope.ts` for the rule
241
+ * and the reason it is validated rather than typed.
242
+ */
243
+ }, {
244
+ key: "opensLocaleScope",
245
+ value: function opensLocaleScope() {
246
+ return null;
247
+ }
248
+
249
+ /**
250
+ * The child schemas a locale scope reaches, by the path segment that reaches
251
+ * them.
252
+ *
253
+ * A structural walk that deliberately does NOT go through `executeSerialize`:
254
+ * serializing a subtree at every node to ask a question about its shape is
255
+ * quadratic, and this is asked during validation. `*` stands for the item of
256
+ * an array or record, which has no name of its own.
257
+ */
258
+ }, {
259
+ key: "localeScopeChildren",
260
+ value: function localeScopeChildren() {
261
+ return [];
262
+ }
263
+
264
+ /**
265
+ * Every locale scope opened strictly BELOW this node, by path.
266
+ *
267
+ * Stops descending at each one it finds, so a scope three deep is reported
268
+ * once, by the scope immediately enclosing it, rather than by every ancestor.
269
+ */
270
+ }, {
271
+ key: "localeScopesBelow",
272
+ value: function localeScopesBelow() {
273
+ var prefix = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
274
+ var found = [];
275
+ var _iterator2 = result._createForOfIteratorHelper(this.localeScopeChildren()),
276
+ _step2;
277
+ try {
278
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
279
+ var _step2$value = _step2.value,
280
+ key = _step2$value.key,
281
+ schema = _step2$value.schema;
282
+ var _path = [].concat(_toConsumableArray(prefix), [key]);
283
+ var opened = schema.opensLocaleScope();
284
+ if (opened !== null) {
285
+ found.push({
286
+ path: _path,
287
+ kind: opened
288
+ });
289
+ continue;
290
+ }
291
+ found.push.apply(found, _toConsumableArray(schema.localeScopesBelow(_path)));
292
+ }
293
+ } catch (err) {
294
+ _iterator2.e(err);
295
+ } finally {
296
+ _iterator2.f();
297
+ }
298
+ return found;
299
+ }
300
+
301
+ /**
302
+ * The scope rule, as errors at this node — or `false` if it is not broken.
303
+ *
304
+ * Two things are wrong and both are wrong in the SCHEMA rather than in the
305
+ * content, so both are `schemaError`s: an object with two locale fields (the
306
+ * subtree below it would be in two languages at once), and a locale scope
307
+ * inside another (the inner one would silently override the outer for part
308
+ * of a subtree that is supposed to be one language throughout).
309
+ *
310
+ * Validated rather than typed on purpose. Expressing "no scope below this
311
+ * one" as a constraint means threading it through every schema class's type
312
+ * parameter, and the errors that fall out of a recursive constraint like
313
+ * that name the whole tree — an unrelated typo in a `.val.ts` would print
314
+ * pages. See the design notes on the locales PR.
315
+ */
316
+ }, {
317
+ key: "localeScopeErrors",
318
+ value: function localeScopeErrors() {
319
+ if (this.localeScopeErrorsMemo === undefined) {
320
+ this.localeScopeErrorsMemo = this.computeLocaleScopeErrors();
321
+ }
322
+ return this.localeScopeErrorsMemo;
323
+ }
324
+ }, {
325
+ key: "computeLocaleScopeErrors",
326
+ value: function computeLocaleScopeErrors() {
327
+ var errors = [];
328
+ var localeFields = this.localeFieldNames();
329
+ if (localeFields.length > 1) {
330
+ errors.push({
331
+ message: "An object can be in one language, so it can have one locale field. Found ".concat(localeFields.map(function (each) {
332
+ return "'".concat(each, "'");
333
+ }).join(", "), "."),
334
+ schemaError: true
335
+ });
336
+ }
337
+ var opened = this.opensLocaleScope();
338
+ if (opened !== null) {
339
+ var _iterator3 = result._createForOfIteratorHelper(this.localeScopesBelow()),
340
+ _step3;
341
+ try {
342
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
343
+ var nested = _step3.value;
344
+ errors.push({
345
+ 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."),
346
+ schemaError: true
347
+ });
348
+ }
349
+ } catch (err) {
350
+ _iterator3.e(err);
351
+ } finally {
352
+ _iterator3.f();
353
+ }
354
+ }
355
+ return errors;
356
+ }
357
+
358
+ /**
359
+ * This node's own `s.locale()` fields, where it is an object that has any.
360
+ *
361
+ * Empty everywhere else, so `localeScopeErrors` can live on the base class.
362
+ */
363
+ }, {
364
+ key: "localeFieldNames",
365
+ value: function localeFieldNames() {
366
+ return [];
367
+ }
368
+ }, {
171
369
  key: "executeCustomValidateFunctions",
172
370
  value: function executeCustomValidateFunctions(src, customValidateFunctions, ctx) {
173
371
  var errors = [];
174
- var _iterator = result._createForOfIteratorHelper(customValidateFunctions),
175
- _step;
372
+ var _iterator4 = result._createForOfIteratorHelper(customValidateFunctions),
373
+ _step4;
176
374
  try {
177
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
178
- var customValidateFunction = _step.value;
375
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
376
+ var customValidateFunction = _step4.value;
179
377
  try {
180
378
  var result$1 = customValidateFunction(src, ctx);
181
379
  if (result$1) {
@@ -193,9 +391,9 @@ var Schema = /*#__PURE__*/function () {
193
391
  }
194
392
  }
195
393
  } catch (err) {
196
- _iterator.e(err);
394
+ _iterator4.e(err);
197
395
  } finally {
198
- _iterator.f();
396
+ _iterator4.f();
199
397
  }
200
398
  return errors;
201
399
  }
@@ -294,11 +492,11 @@ var Schema = /*#__PURE__*/function () {
294
492
  return incoming;
295
493
  }
296
494
  for (var pathS in incoming) {
297
- var _path = pathS;
298
- if (current[_path]) {
299
- current[_path] = current[_path].concat(incoming[_path]);
495
+ var _path2 = pathS;
496
+ if (current[_path2]) {
497
+ current[_path2] = current[_path2].concat(incoming[_path2]);
300
498
  } else {
301
- current[_path] = incoming[_path];
499
+ current[_path2] = incoming[_path2];
302
500
  }
303
501
  }
304
502
  return current;
@@ -333,6 +531,10 @@ var GetSource = Symbol["for"]("@valbuild/core/GetSource");
333
531
  var ValError = Symbol["for"]("@valbuild/core/ValError");
334
532
  var GenericSelector = /*#__PURE__*/_createClass(function GenericSelector(valOrExpr, path, schema, error) {
335
533
  _classCallCheck(this, GenericSelector);
534
+ _defineProperty(this, Path, void 0);
535
+ _defineProperty(this, GetSource, void 0);
536
+ _defineProperty(this, ValError, void 0);
537
+ _defineProperty(this, GetSchema, void 0);
336
538
  this[Path] = path;
337
539
  this[GetSource] = valOrExpr;
338
540
  this[ValError] = error;
@@ -935,6 +1137,18 @@ var ObjectSchema = /*#__PURE__*/function (_Schema) {
935
1137
  } finally {
936
1138
  _iterator.f();
937
1139
  }
1140
+ var _iterator2 = result._createForOfIteratorHelper(this.localeScopeErrors()),
1141
+ _step2;
1142
+ try {
1143
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
1144
+ var scopeError = _step2.value;
1145
+ error = this.appendValidationError(error, path, scopeError.message, src, scopeError.schemaError);
1146
+ }
1147
+ } catch (err) {
1148
+ _iterator2.e(err);
1149
+ } finally {
1150
+ _iterator2.f();
1151
+ }
938
1152
  for (var _i = 0, _Object$entries = Object.entries(this.items); _i < _Object$entries.length; _i++) {
939
1153
  var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
940
1154
  _key = _Object$entries$_i[0],
@@ -1018,7 +1232,7 @@ var ObjectSchema = /*#__PURE__*/function (_Schema) {
1018
1232
  key: "nullable",
1019
1233
  value: function nullable() {
1020
1234
  // Explicit type args: `previewInput` would otherwise pin inference to `Src`.
1021
- return new ObjectSchema(this.items, true, [], this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
1235
+ return new ObjectSchema(this.items, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
1022
1236
  }
1023
1237
  }, {
1024
1238
  key: "readonly",
@@ -1032,6 +1246,35 @@ var ObjectSchema = /*#__PURE__*/function (_Schema) {
1032
1246
  var isHidden = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
1033
1247
  return new ObjectSchema(this.items, this.opt, this.customValidateFunctions, this.isReadonly, isHidden, this.description, this.renderInput, this.previewInput);
1034
1248
  }
1249
+
1250
+ /** The names of this object's `s.locale()` fields, in declaration order. */
1251
+ }, {
1252
+ key: "localeFieldNames",
1253
+ value: function localeFieldNames() {
1254
+ var _this2 = this;
1255
+ return Object.keys(this.items).filter(function (key) {
1256
+ return _this2.items[key]["isLocaleField"]();
1257
+ });
1258
+ }
1259
+ }, {
1260
+ key: "opensLocaleScope",
1261
+ value: function opensLocaleScope() {
1262
+ // More than one is itself the error, reported by `localeScopeErrors`. It
1263
+ // still opens a scope: reporting "two locale fields" AND "a scope inside a
1264
+ // scope" for the same object would be two errors about one mistake.
1265
+ return this.localeFieldNames().length > 0 ? "field" : null;
1266
+ }
1267
+ }, {
1268
+ key: "localeScopeChildren",
1269
+ value: function localeScopeChildren() {
1270
+ var _this3 = this;
1271
+ return Object.keys(this.items).map(function (key) {
1272
+ return {
1273
+ key: key,
1274
+ schema: _this3.items[key]
1275
+ };
1276
+ });
1277
+ }
1035
1278
  }, {
1036
1279
  key: "executeCustomValidateAt",
1037
1280
  value: function executeCustomValidateAt(path, src) {
@@ -1296,7 +1539,7 @@ var ArraySchema = /*#__PURE__*/function (_Schema) {
1296
1539
  key: "nullable",
1297
1540
  value: function nullable() {
1298
1541
  // Explicit type args: `previewInput` would otherwise pin inference to `Src`.
1299
- return new ArraySchema(this.item, true, [], this.isReadonly, this.isHidden, this.description, this.previewInput, this.renderInput);
1542
+ return new ArraySchema(this.item, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.previewInput, this.renderInput);
1300
1543
  }
1301
1544
  }, {
1302
1545
  key: "readonly",
@@ -1310,6 +1553,14 @@ var ArraySchema = /*#__PURE__*/function (_Schema) {
1310
1553
  var isHidden = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
1311
1554
  return new ArraySchema(this.item, this.opt, this.customValidateFunctions, this.isReadonly, isHidden, this.description, this.previewInput, this.renderInput);
1312
1555
  }
1556
+ }, {
1557
+ key: "localeScopeChildren",
1558
+ value: function localeScopeChildren() {
1559
+ return [{
1560
+ key: "*",
1561
+ schema: this.item
1562
+ }];
1563
+ }
1313
1564
  }, {
1314
1565
  key: "executeCustomValidateAt",
1315
1566
  value: function executeCustomValidateAt(path, src) {
@@ -1483,6 +1734,8 @@ var array = function array(schema) {
1483
1734
  };
1484
1735
 
1485
1736
  var LiteralSchema = /*#__PURE__*/function (_Schema) {
1737
+ /** Type-only marker: as a record key, this declares the key set. See `LocaleSchema`. */
1738
+
1486
1739
  function LiteralSchema(value) {
1487
1740
  var _this;
1488
1741
  var opt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
@@ -1614,7 +1867,7 @@ var LiteralSchema = /*#__PURE__*/function (_Schema) {
1614
1867
  }, {
1615
1868
  key: "nullable",
1616
1869
  value: function nullable() {
1617
- return new LiteralSchema(this.value, true, [], this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
1870
+ return new LiteralSchema(this.value, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
1618
1871
  }
1619
1872
  }, {
1620
1873
  key: "readonly",
@@ -2035,7 +2288,7 @@ var DiscriminatedUnionSchema = /*#__PURE__*/function (_Schema) {
2035
2288
  key: "nullable",
2036
2289
  value: function nullable() {
2037
2290
  // Explicit type args: `previewInput` would otherwise pin inference to `Src`.
2038
- return new DiscriminatedUnionSchema(this.key, this.items, true, [], this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
2291
+ return new DiscriminatedUnionSchema(this.key, this.items, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
2039
2292
  }
2040
2293
  }, {
2041
2294
  key: "readonly",
@@ -2049,6 +2302,20 @@ var DiscriminatedUnionSchema = /*#__PURE__*/function (_Schema) {
2049
2302
  var isHidden = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
2050
2303
  return new DiscriminatedUnionSchema(this.key, this.items, this.opt, this.customValidateFunctions, this.isReadonly, isHidden, this.description, this.renderInput, this.previewInput);
2051
2304
  }
2305
+ }, {
2306
+ key: "localeScopeChildren",
2307
+ value: function localeScopeChildren() {
2308
+ // Every variant is walked under the SAME path: whichever one the value
2309
+ // takes, that is where it sits. `s.enum()` has no children at all — its
2310
+ // values are strings, not schemas — which is why only this half of the old
2311
+ // `s.union` has this.
2312
+ return this.items.map(function (item) {
2313
+ return {
2314
+ key: "*",
2315
+ schema: item
2316
+ };
2317
+ });
2318
+ }
2052
2319
  }, {
2053
2320
  key: "executeCustomValidateAt",
2054
2321
  value: function executeCustomValidateAt(path, src) {
@@ -3261,7 +3528,7 @@ var ImageSchema = /*#__PURE__*/function (_Schema) {
3261
3528
  *
3262
3529
  * @example
3263
3530
  * const schema = s
3264
- * .image({ accept: "image/webp", directory: "/public/val/images" })
3531
+ * .image({ accept: "image/webp", dir: "/public/val/images" })
3265
3532
  * .remote();
3266
3533
  * export default c.define("/example.val.ts", schema, {
3267
3534
  * 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",
@@ -3440,6 +3707,10 @@ var ImageSchema = /*#__PURE__*/function (_Schema) {
3440
3707
  /**
3441
3708
  * The entries of the gallery this field points at, or null when it is a
3442
3709
  * standalone field.
3710
+ *
3711
+ * `AltSource` rather than a specific alt type: a field never reads an entry's
3712
+ * alt — it carries its own — so it only needs the entry keys and the
3713
+ * dimensions, and a gallery of any alt shape can back it.
3443
3714
  */
3444
3715
  }, {
3445
3716
  key: "galleryEntries",
@@ -4459,7 +4730,7 @@ var RichTextSchema = /*#__PURE__*/function (_Schema) {
4459
4730
  }, {
4460
4731
  key: "nullable",
4461
4732
  value: function nullable() {
4462
- return new RichTextSchema(this.options, true, [], this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
4733
+ return new RichTextSchema(this.options, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
4463
4734
  }
4464
4735
  }, {
4465
4736
  key: "readonly",
@@ -4976,6 +5247,68 @@ function hasInlineExternalEntries(source) {
4976
5247
  return _typeof(source) === "object" && source !== null && !Array.isArray(source) && !isExternal(source) && Object.keys(source).length > 0;
4977
5248
  }
4978
5249
 
5250
+ /**
5251
+ * The keys a record is REQUIRED to hold, where its key schema says what they are.
5252
+ *
5253
+ * Most records have an open key set — `s.record(s.string(), item)` holds
5254
+ * whatever anyone adds — and this returns `null` for those. A record keyed by
5255
+ * something that enumerates its values is different in kind: the keys are part
5256
+ * of the schema, so a missing one is a hole in the content rather than content
5257
+ * nobody has written yet.
5258
+ *
5259
+ * Two key schemas enumerate. A union of literals does it locally, so its keys
5260
+ * are here. `s.locale()` does it in the settings module, which is another file
5261
+ * and so cannot be read from a serialized schema — that case says only which
5262
+ * kind it is, and is resolved against `locales.available` by
5263
+ * `resolveSchemaSourceFixes`, the same crossing `keyOf` and `route` make.
5264
+ */
5265
+
5266
+ /**
5267
+ * What `key` requires a record to hold, or `null` where it requires nothing.
5268
+ *
5269
+ * Works from the SERIALIZED schema so that the Studio, the validation worker
5270
+ * and the server all answer this the same way — none of them has the schema
5271
+ * instance, and `SchemaStore` deliberately holds serialized schemas only.
5272
+ */
5273
+ function declaredKeySetOf(key) {
5274
+ if (key === undefined) {
5275
+ return null;
5276
+ }
5277
+ if (key.type === "literal") {
5278
+ return {
5279
+ kind: "literals",
5280
+ keys: [key.value]
5281
+ };
5282
+ }
5283
+ if (key.type === "enum") {
5284
+ // An enum names every value it may take, so those values ARE the key set.
5285
+ // `discriminated-union` is deliberately absent: it discriminates objects
5286
+ // and cannot be a record key at all.
5287
+ return {
5288
+ kind: "literals",
5289
+ keys: _toConsumableArray(key.values)
5290
+ };
5291
+ }
5292
+ if (key.type === "locale") {
5293
+ return {
5294
+ kind: "locale"
5295
+ };
5296
+ }
5297
+ return null;
5298
+ }
5299
+
5300
+ /**
5301
+ * The declared keys a record is missing, in the order the schema declares them.
5302
+ *
5303
+ * Declaration order rather than the source's: this is read as "what is left to
5304
+ * write", and the answer should not reshuffle itself as entries are added.
5305
+ */
5306
+ function missingDeclaredKeys(declared, present) {
5307
+ return declared.filter(function (key) {
5308
+ return !present.includes(key);
5309
+ });
5310
+ }
5311
+
4979
5312
  /**
4980
5313
  * The source type of a `.jsonValues()` record: every entry value is EITHER a
4981
5314
  * lazily loaded {@link JsonSource} whose resolved content is the (loosened, see
@@ -5007,6 +5340,16 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5007
5340
  var externalLabel = arguments.length > 12 && arguments[12] !== undefined ? arguments[12] : null;
5008
5341
  _classCallCheck(this, RecordSchema);
5009
5342
  _this = _callSuper(this, RecordSchema);
5343
+ /**
5344
+ * The key set this record's schema declares, computed once.
5345
+ *
5346
+ * `declaredKeySetOf` reads a SERIALIZED schema, and serializing the key
5347
+ * schema walks it — twice per validate, and once per record instance, for an
5348
+ * answer that is a property of the schema and cannot move between them. A
5349
+ * schema is built once and validated against many times, so the walk happens
5350
+ * on the first question and not on any after it.
5351
+ */
5352
+ _defineProperty(_this, "declaredKeys", void 0);
5010
5353
  _this.item = item;
5011
5354
  _this.opt = opt;
5012
5355
  _this.customValidateFunctions = customValidateFunctions;
@@ -5122,9 +5465,9 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5122
5465
  if (this.mediaOptions) {
5123
5466
  var checkFix = this.mediaOptions.type === "images" ? "images:check-unique-folder" : "files:check-unique-folder";
5124
5467
  var uniqueCheckError = {
5125
- message: "Gallery directory '".concat(this.mediaOptions.directory, "' must be unique across all galleries"),
5468
+ message: "Gallery directory '".concat(this.mediaOptions.dir, "' must be unique across all galleries"),
5126
5469
  value: {
5127
- directory: this.mediaOptions.directory,
5470
+ dir: this.mediaOptions.dir,
5128
5471
  type: this.mediaOptions.type
5129
5472
  },
5130
5473
  fixes: [checkFix]
@@ -5140,9 +5483,9 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5140
5483
  }
5141
5484
  var allFilesCheckFix = this.mediaOptions.type === "images" ? "images:check-all-files" : "files:check-all-files";
5142
5485
  var allFilesCheckError = {
5143
- message: "Directory '".concat(this.mediaOptions.directory, "' may have files not tracked by this gallery"),
5486
+ message: "Directory '".concat(this.mediaOptions.dir, "' may have files not tracked by this gallery"),
5144
5487
  value: {
5145
- directory: this.mediaOptions.directory,
5488
+ dir: this.mediaOptions.dir,
5146
5489
  type: this.mediaOptions.type
5147
5490
  },
5148
5491
  fixes: [allFilesCheckFix]
@@ -5153,6 +5496,11 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5153
5496
  error = _objectSpread2(_objectSpread2({}, error), {}, _defineProperty({}, path, [allFilesCheckError]));
5154
5497
  }
5155
5498
  }
5499
+ // A record whose keys are declared by its schema holds every one of them,
5500
+ // and an entry nobody has written yet is `null` rather than absent — so
5501
+ // `null` is a value here, not a value of the wrong type, and the item
5502
+ // schema is not asked about it.
5503
+ var hasDeclaredKeys = this.declaredKeySet() !== null;
5156
5504
  Object.entries(src).forEach(function (_ref5) {
5157
5505
  var _ref6 = _slicedToArray(_ref5, 2),
5158
5506
  key = _ref6[0],
@@ -5192,7 +5540,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5192
5540
  _this2.markKeyErrorsAtPath(entryErr, subPath);
5193
5541
  }
5194
5542
  error = _this2.mergeValidationErrors(error, entryErr);
5195
- } else if (_this2.isJsonValues && isJson(elem)) ; else {
5543
+ } else if (hasDeclaredKeys && elem === null) ; else if (_this2.isJsonValues && isJson(elem)) ; else {
5196
5544
  // Falls through for a jsonValues record whose entry content is inlined
5197
5545
  // (loaded in the UI, or hand-authored): same as a plain record — validate
5198
5546
  // the value against the item schema.
@@ -5200,8 +5548,79 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5200
5548
  error = _this2.mergeValidationErrors(error, subError);
5201
5549
  }
5202
5550
  });
5551
+ var declaredKeys = this.getDeclaredKeysValidation(path, src);
5552
+ error = this.mergeValidationErrors(error, declaredKeys);
5553
+ var _iterator2 = result._createForOfIteratorHelper(this.localeScopeErrors()),
5554
+ _step2;
5555
+ try {
5556
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
5557
+ var scopeError = _step2.value;
5558
+ error = this.appendValidationError(error, path, scopeError.message, src, scopeError.schemaError);
5559
+ }
5560
+ } catch (err) {
5561
+ _iterator2.e(err);
5562
+ } finally {
5563
+ _iterator2.f();
5564
+ }
5203
5565
  return error;
5204
5566
  }
5567
+
5568
+ /**
5569
+ * The check that a record with a DECLARED key set holds every one of them.
5570
+ *
5571
+ * `s.record(s.locale(), item)` has one entry per language and
5572
+ * `s.record(s.union(s.literal("a"), s.literal("b")), item)` has both — the
5573
+ * keys are part of the schema either way, so a missing one is a hole in the
5574
+ * content rather than content nobody has written yet. TypeScript already
5575
+ * demanded both keys of the second; this is the validator catching up, which
5576
+ * is why the two now behave the same.
5577
+ *
5578
+ * Deferred to `resolveSchemaSourceFixes` even for the literal case, where the
5579
+ * keys are known right here: a locale record's keys are in another file, and
5580
+ * one code path for both is one message, one fix and one thing to be wrong.
5581
+ */
5582
+ }, {
5583
+ key: "getDeclaredKeysValidation",
5584
+ value: function getDeclaredKeysValidation(path, src) {
5585
+ if (this.keySchema === null) {
5586
+ return false;
5587
+ }
5588
+ var declared = this.declaredKeySet();
5589
+ if (declared === null) {
5590
+ return false;
5591
+ }
5592
+ return _defineProperty({}, path, [{
5593
+ fixes: ["record:fill-keys"],
5594
+ 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.",
5595
+ value: {
5596
+ present: Object.keys(src),
5597
+ declared: declared.kind === "literals" ? declared.keys : null
5598
+ }
5599
+ }]);
5600
+ }
5601
+ }, {
5602
+ key: "declaredKeySet",
5603
+ value: function declaredKeySet() {
5604
+ if (this.declaredKeys === undefined) {
5605
+ this.declaredKeys = this.keySchema === null ? null : declaredKeySetOf(this.keySchema["executeSerialize"]());
5606
+ }
5607
+ return this.declaredKeys;
5608
+ }
5609
+ }, {
5610
+ key: "opensLocaleScope",
5611
+ value: function opensLocaleScope() {
5612
+ return this.keySchema !== null && this.keySchema["isLocaleField"]() ? "key" : null;
5613
+ }
5614
+ }, {
5615
+ key: "localeScopeChildren",
5616
+ value: function localeScopeChildren() {
5617
+ // The KEY schema is not a child here. A locale key is what opens this
5618
+ // record's scope; it is not something inside the scope.
5619
+ return [{
5620
+ key: "*",
5621
+ schema: this.item
5622
+ }];
5623
+ }
5205
5624
  }, {
5206
5625
  key: "isRemoteUrl",
5207
5626
  value: function isRemoteUrl(url) {
@@ -5227,13 +5646,13 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5227
5646
  return false;
5228
5647
  }
5229
5648
  var _this$mediaOptions = this.mediaOptions,
5230
- directory = _this$mediaOptions.directory,
5649
+ dir = _this$mediaOptions.dir,
5231
5650
  isRemote = _this$mediaOptions.remote,
5232
5651
  type = _this$mediaOptions.type;
5233
5652
  var mediaLabel = type === "images" ? "images" : "files";
5234
5653
  var checkRemoteFix = type === "images" ? "images:check-remote" : "files:check-remote";
5235
5654
  var isRemoteUrl = this.isRemoteUrl(key);
5236
- var isLocalPath = key === directory || key.startsWith(directory + "/");
5655
+ var isLocalPath = key === dir || key.startsWith(dir + "/");
5237
5656
  if (isRemote) {
5238
5657
  // When remote is enabled, accept either remote URLs or local paths
5239
5658
  if (isRemoteUrl) {
@@ -5248,9 +5667,9 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5248
5667
  }
5249
5668
  // Check that the file path in the remote URL matches our directory constraint
5250
5669
  var remotePath = "/" + remoteResult.filePath;
5251
- if (remotePath !== directory && !remotePath.startsWith(directory + "/")) {
5670
+ if (remotePath !== dir && !remotePath.startsWith(dir + "/")) {
5252
5671
  return _defineProperty({}, path, [{
5253
- message: "Remote file path '".concat(remotePath, "' is not in expected directory '").concat(directory, "'. Use Val tooling to upload ").concat(mediaLabel, " to the correct directory."),
5672
+ message: "Remote file path '".concat(remotePath, "' is not in expected directory '").concat(dir, "'. Use Val tooling to upload ").concat(mediaLabel, " to the correct directory."),
5254
5673
  value: key,
5255
5674
  fixes: [checkRemoteFix]
5256
5675
  }]);
@@ -5259,7 +5678,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5259
5678
  }
5260
5679
  if (!isLocalPath) {
5261
5680
  return _defineProperty({}, path, [{
5262
- message: "Expected a remote URL (https://...) or a local path starting with ".concat(directory, "/. Got: ").concat(key),
5681
+ message: "Expected a remote URL (https://...) or a local path starting with ".concat(dir, "/. Got: ").concat(key),
5263
5682
  value: key
5264
5683
  }]);
5265
5684
  }
@@ -5281,7 +5700,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5281
5700
  }
5282
5701
  if (!isLocalPath) {
5283
5702
  return _defineProperty({}, path, [{
5284
- message: "File path must be within the ".concat(directory, "/ directory. Got: ").concat(key),
5703
+ message: "File path must be within the ".concat(dir, "/ directory. Got: ").concat(key),
5285
5704
  value: key
5286
5705
  }]);
5287
5706
  }
@@ -5477,7 +5896,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5477
5896
  * Allow the files in this gallery to be stored on Val's remote content host
5478
5897
  * instead of in your repository.
5479
5898
  *
5480
- * For `s.images()` and `s.files()`, which are records of media. Remote is off
5899
+ * For `s.imageset()` and `s.fileset()`, which are records of media. Remote is off
5481
5900
  * until this is called.
5482
5901
  *
5483
5902
  * Each entry is then keyed by its URL on the content host rather than by a
@@ -5486,7 +5905,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5486
5905
  * upload it and rewrite the key.
5487
5906
  *
5488
5907
  * @example
5489
- * const schema = s.images({ directory: "/public/val/images" }).remote();
5908
+ * const schema = s.imageset({ dir: "/public/val/images" }).remote();
5490
5909
  * export default c.define("/content/images.val.ts", schema, {
5491
5910
  * "https://remote.val.build/file/p/my-project/b/01/v/1.0.0/h/8f2a1c/f/3b9d70/p/public/val/images/hero.webp":
5492
5911
  * {
@@ -5512,7 +5931,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5512
5931
  * which lets the runtime, the Studio and validation work one entry at a time
5513
5932
  * so a record/router can scale to many thousands of entries.
5514
5933
  *
5515
- * Not supported on image/file galleries (`s.images()` / `s.files()`).
5934
+ * Not supported on image/file galleries (`s.imageset()` / `s.fileset()`).
5516
5935
  *
5517
5936
  * Only supported on a module's ROOT record/router — a `.jsonValues()` record
5518
5937
  * nested inside an object/array/record is rejected at startup with a module
@@ -5537,7 +5956,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5537
5956
  key: "jsonValues",
5538
5957
  value: function jsonValues() {
5539
5958
  if (this.mediaOptions) {
5540
- throw new Error(".jsonValues() cannot be used with image/file galleries (s.images()/s.files())");
5959
+ throw new Error(".jsonValues() cannot be used with image/file galleries (s.imageset()/s.fileset())");
5541
5960
  }
5542
5961
  if (this.customValidateFunctions.length > 0) {
5543
5962
  // `.jsonValues()` changes the source shape to JsonSource entries, so a
@@ -5566,7 +5985,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5566
5985
  * bucket — instead of in the module.
5567
5986
  *
5568
5987
  * Available on every record-derived schema, which is `s.record()`,
5569
- * `s.router()`, `s.images()` and `s.files()` alike: a router is a
5988
+ * `s.router()`, `s.imageset()` and `s.fileset()` alike: a router is a
5570
5989
  * `RecordSchema` with a `ValRouter`, a gallery one with media options, so all
5571
5990
  * four get external storage from this one method.
5572
5991
  *
@@ -5721,7 +6140,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5721
6140
  if (this.mediaOptions) {
5722
6141
  result.mediaType = this.mediaOptions.type;
5723
6142
  result.accept = this.mediaOptions.accept;
5724
- result.directory = this.mediaOptions.directory;
6143
+ result.dir = this.mediaOptions.dir;
5725
6144
  result.remote = this.mediaOptions.remote;
5726
6145
  if (this.mediaOptions.encode !== undefined) {
5727
6146
  result.encode = this.mediaOptions.encode;
@@ -5896,6 +6315,23 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5896
6315
  }]);
5897
6316
  }(Schema);
5898
6317
 
6318
+ /**
6319
+ * The source of a record, given what its key schema says about its keys.
6320
+ *
6321
+ * A key schema that ENUMERATES its keys (a union of literals, or `s.locale()`)
6322
+ * makes every one of them an entry of the record, and an entry nobody has
6323
+ * written yet is `null` rather than absent — so the value type widens by
6324
+ * `null`. That is the whole of option F at the type level: a half-translated
6325
+ * record is a record with nulls in it, which is data you can count, filter and
6326
+ * see in a diff, rather than keys that are simply not there.
6327
+ *
6328
+ * For a union of literals TypeScript already required every key, since
6329
+ * `SelectorOfSchema<K>` is the literal union itself. `s.locale()` is a flavoured
6330
+ * string, so it requires nothing here and the requirement is the validator's —
6331
+ * deliberately: which languages exist is in the settings module, where the
6332
+ * project can change it without a deploy.
6333
+ */
6334
+
5899
6335
  // Overload: with key schema
5900
6336
 
5901
6337
  // Overload: without key schema
@@ -5934,6 +6370,31 @@ function record(keyOrSchema, schema) {
5934
6370
  * written without removing the key.
5935
6371
  */
5936
6372
 
6373
+ /**
6374
+ * The languages this project publishes.
6375
+ *
6376
+ * Content rather than configuration, and deliberately: which languages a site
6377
+ * has is a decision the people who write it make, and under a build-time
6378
+ * constant it took a developer and a deploy. It is the same move `assistant`
6379
+ * makes with `enabled`.
6380
+ *
6381
+ * Val ships no list of its own. A project with no `locales` section has not
6382
+ * said it is translated, and nothing about locales appears anywhere — no picker
6383
+ * in the Studio, no checks, nothing.
6384
+ *
6385
+ * **This list is a decision with a blast radius.** Every locale in content is
6386
+ * checked against it, so removing one invalidates the content that uses it, and
6387
+ * adding one leaves every locale-keyed record short of a language until it is
6388
+ * filled in. That is the intended behaviour — a language that is declared and
6389
+ * missing everywhere is worth being told about — but it is why the Studio warns
6390
+ * before saving a removal rather than treating this as an ordinary field.
6391
+ *
6392
+ * There is no default language, deliberately. Every locale-specific field asks
6393
+ * which language it is in, and a default is exactly the answer that lets that
6394
+ * question go unanswered — content ends up filed under a language nobody chose,
6395
+ * which is the state translation is meant to make visible.
6396
+ */
6397
+
5937
6398
  /**
5938
6399
  * The assistant: whether editors have one, and what it is told about the
5939
6400
  * project on every message.
@@ -5994,6 +6455,263 @@ settings) {
5994
6455
  return "unconfigured";
5995
6456
  }
5996
6457
 
6458
+ /**
6459
+ * One of the languages the project publishes.
6460
+ *
6461
+ * The set is NOT here. It is declared in the settings module — `locales.available`
6462
+ * — so that adding a language is something the people who write the content can
6463
+ * do, and so that one list drives validation, the Studio's picker and the
6464
+ * translation notes. This schema knows only that the value is one of them, which
6465
+ * is checked cross-module through the `locale:check-locale` fix, exactly as
6466
+ * `keyOf` and `route` check theirs.
6467
+ *
6468
+ * Never stega encoded (see `stegaEncode.ts`): a locale goes into `<html lang>`,
6469
+ * into `hreflang` and into `Intl` constructors, none of which survive invisible
6470
+ * characters.
6471
+ */
6472
+ var LocaleSchema = /*#__PURE__*/function (_Schema) {
6473
+ /**
6474
+ * Type-only marker: as a record key, this schema DECLARES the key set.
6475
+ *
6476
+ * `declare` so it exists in the type and nowhere at runtime — reading it off
6477
+ * an instance gives `undefined`, and nothing should. It is how `record()`
6478
+ * knows to type entries as `Item | null`: a language nobody has translated
6479
+ * into is a null entry, not an absent key. See `DeclaredKeySet` and
6480
+ * `RecordSrcOf`.
6481
+ */
6482
+
6483
+ function LocaleSchema() {
6484
+ var _this;
6485
+ var opt = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
6486
+ var customValidateFunctions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
6487
+ var isReadonly = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
6488
+ var isHidden = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
6489
+ var description = arguments.length > 4 ? arguments[4] : undefined;
6490
+ var renderInput = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : null;
6491
+ var previewInput = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : null;
6492
+ _classCallCheck(this, LocaleSchema);
6493
+ _this = _callSuper(this, LocaleSchema);
6494
+ _this.opt = opt;
6495
+ _this.customValidateFunctions = customValidateFunctions;
6496
+ _this.isReadonly = isReadonly;
6497
+ _this.isHidden = isHidden;
6498
+ _this.description = description;
6499
+ _this.renderInput = renderInput;
6500
+ _this.previewInput = previewInput;
6501
+ return _this;
6502
+ }
6503
+
6504
+ /**
6505
+ * A note to whoever edits this field, shown under it in the Studio.
6506
+ *
6507
+ * Pass `null` to clear a description set earlier.
6508
+ *
6509
+ * @example
6510
+ * const schema = s.object({
6511
+ * locale: s.locale().describe("The language everything below is written in"),
6512
+ * title: s.string(),
6513
+ * });
6514
+ * export default c.define("/example.val.ts", schema, {
6515
+ * locale: "nb-NO",
6516
+ * title: "Vinterjakke",
6517
+ * });
6518
+ */
6519
+ _inherits(LocaleSchema, _Schema);
6520
+ return _createClass(LocaleSchema, [{
6521
+ key: "describe",
6522
+ value: function describe(description) {
6523
+ return new LocaleSchema(this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.renderInput, this.previewInput);
6524
+ }
6525
+
6526
+ /**
6527
+ * A check of your own, on top of "is this one of the project's languages".
6528
+ *
6529
+ * Return a message when the value is WRONG and `false` when it is fine — the
6530
+ * two answers are a complaint and no complaint, so write the check as a
6531
+ * ternary rather than as `ok || "message"`, which returns `true` and `true`
6532
+ * is neither.
6533
+ *
6534
+ * @example
6535
+ * const schema = s.object({
6536
+ * locale: s.locale().validate((val) =>
6537
+ * val === "en-US" ? "This section is not translated into English yet" : false,
6538
+ * ),
6539
+ * title: s.string(),
6540
+ * });
6541
+ * export default c.define("/example.val.ts", schema, {
6542
+ * locale: "nb-NO",
6543
+ * title: "Vinterjakke",
6544
+ * });
6545
+ */
6546
+ }, {
6547
+ key: "validate",
6548
+ value: function validate(validationFunction) {
6549
+ return new LocaleSchema(this.opt, this.customValidateFunctions.concat(validationFunction), this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
6550
+ }
6551
+ }, {
6552
+ key: "executeValidate",
6553
+ value: function executeValidate(path, src) {
6554
+ var customValidationErrors = this.executeCustomValidateFunctions(src, this.customValidateFunctions, {
6555
+ path: path
6556
+ });
6557
+ if (this.opt && (src === null || src === undefined)) {
6558
+ return customValidationErrors.length > 0 ? _defineProperty({}, path, customValidationErrors) : false;
6559
+ }
6560
+ if (typeof src !== "string") {
6561
+ return _defineProperty({}, path, [{
6562
+ message: "Expected 'string', got '".concat(_typeof(src), "'"),
6563
+ value: src
6564
+ }]);
6565
+ }
6566
+ return _defineProperty({}, path, [].concat(_toConsumableArray(customValidationErrors), [{
6567
+ // Which languages exist is in another module, so this cannot be
6568
+ // answered here — the same shape `keyOf` and `route` use, resolved by
6569
+ // `resolveSchemaSourceFixes` against the settings module.
6570
+ fixes: ["locale:check-locale"],
6571
+ 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.",
6572
+ value: {
6573
+ locale: src,
6574
+ sourcePath: path
6575
+ }
6576
+ }]));
6577
+ }
6578
+ }, {
6579
+ key: "executeAssert",
6580
+ value: function executeAssert(path, src) {
6581
+ if (this.opt && src === null) {
6582
+ return {
6583
+ success: true,
6584
+ data: src
6585
+ };
6586
+ }
6587
+ if (typeof src === "string") {
6588
+ return {
6589
+ success: true,
6590
+ data: src
6591
+ };
6592
+ }
6593
+ return {
6594
+ success: false,
6595
+ errors: _defineProperty({}, path, [{
6596
+ message: "Expected 'string', got '".concat(_typeof(src), "'"),
6597
+ typeError: true
6598
+ }])
6599
+ };
6600
+ }
6601
+ }, {
6602
+ key: "nullable",
6603
+ value: function nullable() {
6604
+ // Asserted whole, exactly as `RouteSchema.nullable` does: the source type
6605
+ // widens by `null` while the caller's validators and preview stay typed
6606
+ // against the value, and `executeValidate` returns before either sees one.
6607
+ return new LocaleSchema(true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
6608
+ }
6609
+ }, {
6610
+ key: "readonly",
6611
+ value: function readonly() {
6612
+ return new LocaleSchema(this.opt, this.customValidateFunctions, true, this.isHidden, this.description, this.renderInput, this.previewInput);
6613
+ }
6614
+ }, {
6615
+ key: "hidden",
6616
+ value: function hidden() {
6617
+ return new LocaleSchema(this.opt, this.customValidateFunctions, this.isReadonly, true, this.description, this.renderInput, this.previewInput);
6618
+ }
6619
+ }, {
6620
+ key: "isLocaleField",
6621
+ value: function isLocaleField() {
6622
+ return true;
6623
+ }
6624
+ }, {
6625
+ key: "executeCustomValidateAt",
6626
+ value: function executeCustomValidateAt(path, src) {
6627
+ return this.executeCustomValidateFunctions(src, this.customValidateFunctions, {
6628
+ path: path
6629
+ });
6630
+ }
6631
+
6632
+ /**
6633
+ * How this field is laid out where it is the item of an array or a record.
6634
+ * Static configuration, not a callback — see `render.ts`.
6635
+ *
6636
+ * @example
6637
+ * const schema = s.array(s.locale().render({ as: "inline" }));
6638
+ * export default c.define("/example.val.ts", schema, ["nb-NO", "en-US"]);
6639
+ */
6640
+ }, {
6641
+ key: "render",
6642
+ value: function render(input) {
6643
+ return new LocaleSchema(this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, input, this.previewInput);
6644
+ }
6645
+
6646
+ /**
6647
+ * How this VALUE is shown where a preview of it is needed. Never how the field
6648
+ * itself is edited (that is `render`). See `preview.ts`.
6649
+ *
6650
+ * @example
6651
+ * const schema = s.array(
6652
+ * s.locale().preview(({ val }) => ({ title: val })),
6653
+ * );
6654
+ * export default c.define("/example.val.ts", schema, ["nb-NO", "en-US"]);
6655
+ */
6656
+ }, {
6657
+ key: "preview",
6658
+ value: function preview(select) {
6659
+ return new LocaleSchema(this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, select);
6660
+ }
6661
+ }, {
6662
+ key: "executePreviewItem",
6663
+ value: function executePreviewItem(src) {
6664
+ if (this.previewInput === null) {
6665
+ return null;
6666
+ }
6667
+ return this.previewInput({
6668
+ val: src
6669
+ });
6670
+ }
6671
+ }, {
6672
+ key: "declaresItemPreview",
6673
+ value: function declaresItemPreview() {
6674
+ return this.previewInput !== null;
6675
+ }
6676
+ }, {
6677
+ key: "executeSerialize",
6678
+ value: function executeSerialize() {
6679
+ var _this$renderInput, _this$customValidateF;
6680
+ return {
6681
+ type: "locale",
6682
+ render: (_this$renderInput = this.renderInput) !== null && _this$renderInput !== void 0 ? _this$renderInput : undefined,
6683
+ preview: this.previewInput ? true : undefined,
6684
+ opt: this.opt,
6685
+ customValidate: this.customValidateFunctions && ((_this$customValidateF = this.customValidateFunctions) === null || _this$customValidateF === void 0 ? void 0 : _this$customValidateF.length) > 0,
6686
+ readonly: this.isReadonly,
6687
+ hidden: this.isHidden,
6688
+ description: this.description
6689
+ };
6690
+ }
6691
+ }, {
6692
+ key: "executePreview",
6693
+ value: function executePreview() {
6694
+ return {};
6695
+ }
6696
+ }]);
6697
+ }(Schema);
6698
+
6699
+ /**
6700
+ * Define one of the project's languages.
6701
+ *
6702
+ * The languages themselves are declared in the settings module, under
6703
+ * `locales.available` — this says only that a value is one of them.
6704
+ *
6705
+ * @example // a field: everything under this object is in this language
6706
+ * const schema = s.object({ locale: s.locale(), title: s.string() });
6707
+ *
6708
+ * @example // a key: one entry per language
6709
+ * const schema = s.record(s.locale(), s.object({ title: s.string() }));
6710
+ */
6711
+ var locale = function locale() {
6712
+ return new LocaleSchema();
6713
+ };
6714
+
5997
6715
  var BooleanSchema = /*#__PURE__*/function (_Schema) {
5998
6716
  function BooleanSchema() {
5999
6717
  var _this;
@@ -6111,7 +6829,7 @@ var BooleanSchema = /*#__PURE__*/function (_Schema) {
6111
6829
  }, {
6112
6830
  key: "nullable",
6113
6831
  value: function nullable() {
6114
- return new BooleanSchema(true, [], this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
6832
+ return new BooleanSchema(true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
6115
6833
  }
6116
6834
  }, {
6117
6835
  key: "readonly",
@@ -6207,6 +6925,99 @@ var _boolean = function _boolean() {
6207
6925
  return new BooleanSchema();
6208
6926
  };
6209
6927
 
6928
+ /**
6929
+ * Language tags, and what makes one wrong.
6930
+ *
6931
+ * Val does not ship a list of locales. Which languages a project publishes is a
6932
+ * property of that project, so it is declared in the settings module — see
6933
+ * `LocalesSettingsSource` — and everything downstream reads it from there.
6934
+ *
6935
+ * What Val does own is the SPELLING. A tag is checked against BCP 47
6936
+ * (RFC 5646) through `Intl.getCanonicalLocales`, which is the same
6937
+ * implementation the browser, `<html lang>` and every `Intl` constructor use.
6938
+ * Delegating rather than writing a matcher is the point: a tag this accepts is
6939
+ * a tag those accept.
6940
+ */
6941
+
6942
+ /**
6943
+ * What is wrong with `tag` as a language tag, or `false` if nothing is.
6944
+ *
6945
+ * Canonical form, not merely parseable, and the difference is what makes this
6946
+ * worth checking at all. `nb-no` parses; `Intl.DisplayNames` and a
6947
+ * case-sensitive comparison against a stored key do not agree about it. Since a
6948
+ * locale is compared as a string everywhere — a record key, a URL segment, a
6949
+ * value in content — one spelling has to win, and the canonical one is the
6950
+ * spelling every other tool already produces.
6951
+ *
6952
+ * The message names the canonical form where there is one, because "not
6953
+ * canonical" is not an instruction and `nb-NO` is.
6954
+ */
6955
+ function localeTagError(tag) {
6956
+ var canonical;
6957
+ try {
6958
+ canonical = Intl.getCanonicalLocales(tag);
6959
+ } catch (_unused) {
6960
+ // RangeError, for anything the grammar rejects: an underscore instead of a
6961
+ // hyphen (`nb_NO`, which is POSIX rather than BCP 47), an empty subtag, a
6962
+ // subtag of the wrong length.
6963
+ return "'".concat(tag, "' is not a language tag. Language then region, separated by a hyphen \u2014 'nb-NO', not 'nb_NO'");
6964
+ }
6965
+ if (canonical.length !== 1) {
6966
+ // A comma-separated list, or something that canonicalised to nothing.
6967
+ return "'".concat(tag, "' is not a single language tag");
6968
+ }
6969
+ if (canonical[0] !== tag) {
6970
+ return "'".concat(tag, "' is not canonical. Write it as '").concat(canonical[0], "'");
6971
+ }
6972
+ return false;
6973
+ }
6974
+
6975
+ /**
6976
+ * A language tag, as content holds it.
6977
+ *
6978
+ * A *flavoured* string rather than a union or a strict brand. Which languages a
6979
+ * project has is declared in its settings module, where the project can change
6980
+ * it, so there is no set for a type to enumerate — and a strict brand would make
6981
+ * `"nb-NO"` unassignable, which would mean writing every locale in a `.val.ts`
6982
+ * through a constructor. Content stays plain data.
6983
+ *
6984
+ * So this carries no compile-time guarantee, and is not meant to. It names the
6985
+ * concept where a bare `string` would say nothing, and the guarantee is the
6986
+ * `locale:check-locale` validation against `locales.available`.
6987
+ */
6988
+
6989
+ /**
6990
+ * The locale a stored value means, or `null` if it means none of them.
6991
+ *
6992
+ * A lookup against the project's languages, and today that is all it is. It
6993
+ * stays a named function because it is the one place the question is asked —
6994
+ * the Studio, the validation worker and the server all come here rather than
6995
+ * comparing strings themselves, so they cannot disagree about what a stored
6996
+ * value is.
6997
+ */
6998
+ function localeOfValue(value, available) {
6999
+ return available.includes(value) ? value : null;
7000
+ }
7001
+
7002
+ /**
7003
+ * A rule about a section that its individual fields cannot state.
7004
+ *
7005
+ * Paths are WITHIN the section, so an error lands on the value it is about —
7006
+ * including a row of a list, which is what makes this different from a
7007
+ * `.validate()` on the field.
7008
+ */
7009
+
7010
+ /**
7011
+ * Every section's rules, by the name that travels in the serialized schema.
7012
+ *
7013
+ * Typed with `| undefined` on the value rather than as a `Partial`: the whole
7014
+ * point is that a lookup can miss, because the name comes off a serialized
7015
+ * schema that may have been written by a newer Val than the one reading it.
7016
+ */
7017
+ var sectionValidators = {
7018
+ locales: localesSectionErrors
7019
+ };
7020
+
6210
7021
  /**
6211
7022
  * An object whose keys are all optional: absent means unset.
6212
7023
  *
@@ -6225,6 +7036,23 @@ var SettingsSchema = /*#__PURE__*/function (_Schema) {
6225
7036
  var isReadonly = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
6226
7037
  var isHidden = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
6227
7038
  var description = arguments.length > 4 ? arguments[4] : undefined;
7039
+ var
7040
+ /**
7041
+ * Which section this is, where Val has rules about it beyond its keys.
7042
+ *
7043
+ * Not the `validate` the class deliberately does not offer: that one is the
7044
+ * schema author's, and `s.settings()` takes no arguments to declare it
7045
+ * with. This names a rule Val states about a shape Val owns — no language
7046
+ * declared twice in `locales.available` is true of every project, and
7047
+ * there is nowhere else it can be said. A single value's rules stay on its
7048
+ * own schema; this is for the ones that need to see more than one value,
7049
+ * or to report on a row of a list.
7050
+ *
7051
+ * A NAME rather than the function, so it survives serialization: the schema
7052
+ * reaches the Studio's validation worker as JSON, and a closure would be
7053
+ * dropped on the way — which is how these rules once went missing there.
7054
+ */
7055
+ section = arguments.length > 5 ? arguments[5] : undefined;
6228
7056
  _classCallCheck(this, SettingsSchema);
6229
7057
  _this = _callSuper(this, SettingsSchema);
6230
7058
  _this.items = items;
@@ -6232,10 +7060,24 @@ var SettingsSchema = /*#__PURE__*/function (_Schema) {
6232
7060
  _this.isReadonly = isReadonly;
6233
7061
  _this.isHidden = isHidden;
6234
7062
  _this.description = description;
7063
+ _this.section = section;
6235
7064
  return _this;
6236
7065
  }
7066
+
7067
+ /**
7068
+ * This section's rules, or `undefined` where there are none to run.
7069
+ *
7070
+ * A name this Val does not know means a schema written by a newer one. Its
7071
+ * per-field validation still runs; only the section's own rule is missed,
7072
+ * which is the mild half of the failure and better than refusing the schema.
7073
+ */
6237
7074
  _inherits(SettingsSchema, _Schema);
6238
7075
  return _createClass(SettingsSchema, [{
7076
+ key: "sectionValidate",
7077
+ value: function sectionValidate() {
7078
+ return this.section === undefined ? undefined : sectionValidators[this.section];
7079
+ }
7080
+ }, {
6239
7081
  key: "executeValidate",
6240
7082
  value: function executeValidate(path, src) {
6241
7083
  var error = false;
@@ -6288,6 +7130,39 @@ var SettingsSchema = /*#__PURE__*/function (_Schema) {
6288
7130
  }).join(", ")), src, true);
6289
7131
  }
6290
7132
  }
7133
+ var sectionValidate = this.sectionValidate();
7134
+ if (sectionValidate) {
7135
+ var _iterator = result._createForOfIteratorHelper(sectionValidate(src)),
7136
+ _step;
7137
+ try {
7138
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
7139
+ var _ref4;
7140
+ var _step$value = _step.value,
7141
+ keys = _step$value.path,
7142
+ message = _step$value.message;
7143
+ var _subPath = path;
7144
+ var _iterator2 = result._createForOfIteratorHelper(keys),
7145
+ _step2;
7146
+ try {
7147
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
7148
+ var _key3 = _step2.value;
7149
+ _subPath = _subPath && createValPathOfItem(_subPath, _key3);
7150
+ }
7151
+ } catch (err) {
7152
+ _iterator2.e(err);
7153
+ } finally {
7154
+ _iterator2.f();
7155
+ }
7156
+ error = this.appendValidationError(error, // Falls back to the section itself, which is where an error belongs
7157
+ // when the value it is about is not addressable.
7158
+ (_ref4 = _subPath) !== null && _ref4 !== void 0 ? _ref4 : path, message, src);
7159
+ }
7160
+ } catch (err) {
7161
+ _iterator.e(err);
7162
+ } finally {
7163
+ _iterator.f();
7164
+ }
7165
+ }
6291
7166
  return error;
6292
7167
  }
6293
7168
  }, {
@@ -6333,17 +7208,17 @@ var SettingsSchema = /*#__PURE__*/function (_Schema) {
6333
7208
  }, {
6334
7209
  key: "nullable",
6335
7210
  value: function nullable() {
6336
- return new SettingsSchema(this.items, true, this.isReadonly, this.isHidden, this.description);
7211
+ return new SettingsSchema(this.items, true, this.isReadonly, this.isHidden, this.description, this.section);
6337
7212
  }
6338
7213
  }, {
6339
7214
  key: "readonly",
6340
7215
  value: function readonly() {
6341
- return new SettingsSchema(this.items, this.opt, true, this.isHidden, this.description);
7216
+ return new SettingsSchema(this.items, this.opt, true, this.isHidden, this.description, this.section);
6342
7217
  }
6343
7218
  }, {
6344
7219
  key: "hidden",
6345
7220
  value: function hidden() {
6346
- return new SettingsSchema(this.items, this.opt, this.isReadonly, true, this.description);
7221
+ return new SettingsSchema(this.items, this.opt, this.isReadonly, true, this.description, this.section);
6347
7222
  }
6348
7223
  }, {
6349
7224
  key: "executeCustomValidateAt",
@@ -6358,16 +7233,17 @@ var SettingsSchema = /*#__PURE__*/function (_Schema) {
6358
7233
  value: function executeSerialize() {
6359
7234
  return {
6360
7235
  type: "settings",
6361
- items: Object.fromEntries(Object.entries(this.items).map(function (_ref4) {
6362
- var _ref5 = _slicedToArray(_ref4, 2),
6363
- key = _ref5[0],
6364
- schema = _ref5[1];
7236
+ items: Object.fromEntries(Object.entries(this.items).map(function (_ref5) {
7237
+ var _ref6 = _slicedToArray(_ref5, 2),
7238
+ key = _ref6[0],
7239
+ schema = _ref6[1];
6365
7240
  return [key, schema["executeSerialize"]()];
6366
7241
  })),
6367
7242
  opt: this.opt,
6368
7243
  readonly: this.isReadonly,
6369
7244
  hidden: this.isHidden,
6370
- description: this.description
7245
+ description: this.description,
7246
+ section: this.section
6371
7247
  };
6372
7248
  }
6373
7249
  }, {
@@ -6377,19 +7253,19 @@ var SettingsSchema = /*#__PURE__*/function (_Schema) {
6377
7253
  if (src === null) {
6378
7254
  return res;
6379
7255
  }
6380
- for (var _key3 in this.items) {
6381
- var itemSrc = src[_key3];
7256
+ for (var _key4 in this.items) {
7257
+ var itemSrc = src[_key4];
6382
7258
  if (itemSrc === null || itemSrc === undefined) {
6383
7259
  continue;
6384
7260
  }
6385
- var subPath = unsafeCreateSourcePath(sourcePath, _key3);
7261
+ var subPath = unsafeCreateSourcePath(sourcePath, _key4);
6386
7262
  if (scope !== undefined && !scope.wantsUnder(subPath)) {
6387
7263
  continue;
6388
7264
  }
6389
- var itemResult = this.items[_key3]["executePreview"](subPath, itemSrc, scope);
7265
+ var itemResult = this.items[_key4]["executePreview"](subPath, itemSrc, scope);
6390
7266
  for (var keyS in itemResult) {
6391
- var _key4 = keyS;
6392
- res[_key4] = itemResult[_key4];
7267
+ var _key5 = keyS;
7268
+ res[_key5] = itemResult[_key5];
6393
7269
  }
6394
7270
  }
6395
7271
  return res;
@@ -6433,11 +7309,58 @@ function settings() {
6433
7309
  assistant: new SettingsSchema({
6434
7310
  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."),
6435
7311
  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."),
6436
- 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.")
6437
- })
7312
+ 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."),
7313
+ 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.")
7314
+ }),
7315
+ locales: new SettingsSchema({
7316
+ // Nullable because the source type is, and because the Studio writes
7317
+ // `null` for every sibling the first time a section is created.
7318
+ 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.")
7319
+ }, false, false, false, undefined, "locales")
6438
7320
  });
6439
7321
  }
6440
7322
 
7323
+ /**
7324
+ * The rules about `locales` that need to see the whole list.
7325
+ *
7326
+ * Two of them, and both report on the ENTRY rather than the list: a tag has to
7327
+ * be spelled canonically, and a language cannot be declared twice. Neither can
7328
+ * live on the item schema — the item sees one string, and "twice" is a fact
7329
+ * about its siblings — and neither can be a `.validate()` on the array either,
7330
+ * since that reports one message on the array itself rather than on the row to
7331
+ * delete.
7332
+ *
7333
+ * Reads defensively rather than asserting the source's type. A settings module
7334
+ * is a file someone edits by hand, and validation runs against whatever is in
7335
+ * it — including the shapes the per-key pass is, in the same breath, reporting.
7336
+ */
7337
+ function localesSectionErrors(src) {
7338
+ var errors = [];
7339
+ var raw = Array.isArray(src["available"]) ? src["available"] : [];
7340
+ var seen = new Set();
7341
+ for (var i = 0; i < raw.length; i++) {
7342
+ var tag = raw[i];
7343
+ if (typeof tag !== "string") {
7344
+ // The item's own schema has already said what is wrong with it.
7345
+ continue;
7346
+ }
7347
+ var spelling = localeTagError(tag);
7348
+ if (spelling !== false) {
7349
+ errors.push({
7350
+ path: ["available", i],
7351
+ message: spelling
7352
+ });
7353
+ } else if (seen.has(tag)) {
7354
+ errors.push({
7355
+ path: ["available", i],
7356
+ message: "'".concat(tag, "' is declared twice")
7357
+ });
7358
+ }
7359
+ seen.add(tag);
7360
+ }
7361
+ return errors;
7362
+ }
7363
+
6441
7364
  /**
6442
7365
  * What may be WRITTEN as an external record's source, on top of the marker.
6443
7366
  *
@@ -7151,7 +8074,7 @@ var NumberSchema = /*#__PURE__*/function (_Schema) {
7151
8074
  }, {
7152
8075
  key: "nullable",
7153
8076
  value: function nullable() {
7154
- return new NumberSchema(this.options, true, [], this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
8077
+ return new NumberSchema(this.options, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
7155
8078
  }
7156
8079
  }, {
7157
8080
  key: "readonly",
@@ -7288,6 +8211,15 @@ var number = function number(options) {
7288
8211
  * discriminated union's variants and must not try to recurse into these.
7289
8212
  */
7290
8213
  var EnumSchema = /*#__PURE__*/function (_Schema) {
8214
+ /**
8215
+ * Type-only marker: as a record key, this declares the key set.
8216
+ *
8217
+ * An enum names every value it may take, so a record keyed by one holds
8218
+ * exactly those keys — the same claim `s.literal()` and `s.locale()` make.
8219
+ * `DiscriminatedUnionSchema` carries no marker: it discriminates objects and
8220
+ * cannot be a record key at all. See `LocaleSchema`.
8221
+ */
8222
+
7291
8223
  function EnumSchema(values) {
7292
8224
  var _this;
7293
8225
  var opt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
@@ -7441,7 +8373,7 @@ var EnumSchema = /*#__PURE__*/function (_Schema) {
7441
8373
  key: "nullable",
7442
8374
  value: function nullable() {
7443
8375
  // Explicit type args: `previewInput` would otherwise pin inference to `Src`.
7444
- return new EnumSchema(this.values, true, [], this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
8376
+ return new EnumSchema(this.values, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
7445
8377
  }
7446
8378
  }, {
7447
8379
  key: "readonly",
@@ -7832,7 +8764,7 @@ var KeyOfSchema = /*#__PURE__*/function (_Schema) {
7832
8764
  }, {
7833
8765
  key: "nullable",
7834
8766
  value: function nullable() {
7835
- return new KeyOfSchema(this.schema, this.sourcePath, true, [], this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
8767
+ return new KeyOfSchema(this.schema, this.sourcePath, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
7836
8768
  }
7837
8769
  }, {
7838
8770
  key: "readonly",
@@ -8380,7 +9312,7 @@ function file(options) {
8380
9312
  }
8381
9313
 
8382
9314
  /**
8383
- * Options for s.files()
9315
+ * Options for s.fileset()
8384
9316
  */
8385
9317
 
8386
9318
  /**
@@ -8394,9 +9326,9 @@ function file(options) {
8394
9326
  *
8395
9327
  * @example
8396
9328
  * ```typescript
8397
- * const schema = s.files({
9329
+ * const schema = s.fileset({
8398
9330
  * accept: "application/pdf",
8399
- * directory: "/public/val/documents",
9331
+ * dir: "/public/val/documents",
8400
9332
  * });
8401
9333
  * export default c.define("/content/documents.val.ts", schema, {
8402
9334
  * "/public/val/documents/report.pdf": {
@@ -8405,15 +9337,15 @@ function file(options) {
8405
9337
  * });
8406
9338
  * ```
8407
9339
  */
8408
- var files = function files(options) {
8409
- var directory = options.directory;
9340
+ var fileset = function fileset(options) {
9341
+ var dir = options.dir;
8410
9342
  var itemSchema = new ObjectSchema({
8411
9343
  mimeType: new StringSchema({}, false)
8412
9344
  }, false);
8413
9345
  return new RecordSchema(itemSchema, false, [], null, null, {
8414
9346
  type: "files",
8415
9347
  accept: options.accept,
8416
- directory: directory,
9348
+ dir: dir,
8417
9349
  remote: false
8418
9350
  });
8419
9351
  };
@@ -8606,7 +9538,7 @@ var DateSchema = /*#__PURE__*/function (_Schema) {
8606
9538
  }, {
8607
9539
  key: "nullable",
8608
9540
  value: function nullable() {
8609
- return new DateSchema(this.options, true, [], this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
9541
+ return new DateSchema(this.options, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
8610
9542
  }
8611
9543
  }, {
8612
9544
  key: "readonly",
@@ -8942,7 +9874,7 @@ var DateTimeSchema = /*#__PURE__*/function (_Schema) {
8942
9874
  }, {
8943
9875
  key: "nullable",
8944
9876
  value: function nullable() {
8945
- return new DateTimeSchema(this.options, true, [], this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
9877
+ return new DateTimeSchema(this.options, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
8946
9878
  }
8947
9879
  }, {
8948
9880
  key: "readonly",
@@ -9190,7 +10122,7 @@ var CodeSchema = /*#__PURE__*/function (_Schema) {
9190
10122
  }, {
9191
10123
  key: "nullable",
9192
10124
  value: function nullable() {
9193
- return new CodeSchema(this.options, true, [], this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
10125
+ return new CodeSchema(this.options, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
9194
10126
  }
9195
10127
  }, {
9196
10128
  key: "readonly",
@@ -9938,7 +10870,7 @@ var ColorSchema = /*#__PURE__*/function (_Schema) {
9938
10870
  }, {
9939
10871
  key: "nullable",
9940
10872
  value: function nullable() {
9941
- return new ColorSchema(this.options, true, [], this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
10873
+ return new ColorSchema(this.options, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput);
9942
10874
  }
9943
10875
  }, {
9944
10876
  key: "readonly",
@@ -10044,14 +10976,37 @@ function router(router, keyOrItem, maybeItem) {
10044
10976
  */
10045
10977
 
10046
10978
  /**
10047
- * Options for s.images()
10979
+ * What an entry's `alt` holds, for a given `alt` schema.
10980
+ *
10981
+ * This is the schema's own source type, not a second list that has to be kept
10982
+ * in step with `AltSchema`: `s.string()` gives `string`, `s.string().nullable()`
10983
+ * gives `string | null`, and `s.record(s.string())` gives
10984
+ * `Record<string, string>`.
10048
10985
  */
10049
10986
 
10987
+ /** Every `alt` value any `AltSchema` can produce. */
10988
+
10050
10989
  /**
10051
- * Metadata for an image entry in the images record
10990
+ * Options for s.imageset()
10052
10991
  */
10053
10992
 
10054
- // Item schema types for images (alt simplified to string | null for typing)
10993
+ /**
10994
+ * `alt` is optional only when it is the default schema.
10995
+ *
10996
+ * Naming a different `Alt` and then leaving `alt` out would make the fallback
10997
+ * in `imageset()` a lie — `Alt` would say `s.string()` while the value was a
10998
+ * nullable one. Requiring it in that case is what makes the fallback sound:
10999
+ * the branch that uses it is reachable only when `Alt` IS the default.
11000
+ */
11001
+
11002
+ /**
11003
+ * Metadata for an image entry in the images record.
11004
+ *
11005
+ * `alt` follows the schema's `alt` option, so it defaults to `string | null`
11006
+ * and becomes `string` under `s.string()` or `Record<string, string>` under
11007
+ * `s.record(s.string())`. Write `ImagesetEntryMetadata<AltSource>` where a
11008
+ * gallery of any alt shape is acceptable.
11009
+ */
10055
11010
 
10056
11011
  /**
10057
11012
  * Define a collection of images.
@@ -10062,9 +11017,9 @@ function router(router, keyOrItem, maybeItem) {
10062
11017
  *
10063
11018
  * @example
10064
11019
  * ```typescript
10065
- * const schema = s.images({
11020
+ * const schema = s.imageset({
10066
11021
  * accept: "image/webp",
10067
- * directory: "/public/val/images",
11022
+ * dir: "/public/val/images",
10068
11023
  * alt: s.string().minLength(4),
10069
11024
  * });
10070
11025
  * export default c.define("/content/images.val.ts", schema, {
@@ -10077,9 +11032,18 @@ function router(router, keyOrItem, maybeItem) {
10077
11032
  * });
10078
11033
  * ```
10079
11034
  */
10080
- var images = function images(options) {
11035
+ var imageset = function imageset(options) {
10081
11036
  var _options$alt, _options$accept;
10082
- var directory = options.directory;
11037
+ var dir = options.dir;
11038
+ // `options.alt` is `Alt | undefined`, and the fallback is exactly the schema
11039
+ // `Alt` defaults to when `alt` is omitted. TypeScript will not narrow a type
11040
+ // parameter from the absence of a value, so it cannot see that the two agree,
11041
+ // and the fallback is asserted here.
11042
+ //
11043
+ // Sound, not merely convenient: `ImagesetAltOption` makes `alt` REQUIRED
11044
+ // unless `Alt` is the default, so this branch is unreachable for any other
11045
+ // `Alt`. The one assertion in this file, and it replaces the wider one that
11046
+ // used to sit on the ObjectSchema below.
10083
11047
  var altSchema = (_options$alt = options.alt) !== null && _options$alt !== void 0 ? _options$alt : string().nullable();
10084
11048
  var itemSchema = new ObjectSchema({
10085
11049
  width: new NumberSchema(undefined, false),
@@ -10090,7 +11054,7 @@ var images = function images(options) {
10090
11054
  return new RecordSchema(itemSchema, false, [], null, null, {
10091
11055
  type: "images",
10092
11056
  accept: (_options$accept = options.accept) !== null && _options$accept !== void 0 ? _options$accept : "image/*",
10093
- directory: directory,
11057
+ dir: dir,
10094
11058
  remote: false,
10095
11059
  altSchema: altSchema,
10096
11060
  encode: options.encode
@@ -10122,14 +11086,15 @@ function initSchema() {
10122
11086
  keyOf: keyOf,
10123
11087
  record: record,
10124
11088
  file: file,
10125
- files: files,
11089
+ fileset: fileset,
10126
11090
  date: date,
10127
11091
  datetime: datetime,
10128
11092
  color: color,
10129
11093
  code: code,
11094
+ locale: locale,
10130
11095
  route: route,
10131
11096
  router: router,
10132
- images: images,
11097
+ imageset: imageset,
10133
11098
  settings: settings
10134
11099
  // i18n: i18n(locales),
10135
11100
  };
@@ -10265,6 +11230,8 @@ var Hash = /*#__PURE__*/function () {
10265
11230
  _defineProperty(this, "F", 0x9b05688c | 0);
10266
11231
  _defineProperty(this, "G", 0x1f83d9ab | 0);
10267
11232
  _defineProperty(this, "H", 0x5be0cd19 | 0);
11233
+ _defineProperty(this, "_byte", void 0);
11234
+ _defineProperty(this, "_word", void 0);
10268
11235
  _defineProperty(this, "_size", 0);
10269
11236
  _defineProperty(this, "_sp", 0);
10270
11237
  if (!sharedBuffer || sharedOffset >= N.allocTotal) {
@@ -10969,7 +11936,7 @@ function _extractValModules() {
10969
11936
  * The one answer to that question. There used to be two — `hasRemoteFileSchema`
10970
11937
  * in the server, gating whether `/save` demands remote credentials, and
10971
11938
  * `findRequiredRemoteFiles` in the Studio, gating the `/remote/settings` fetch —
10972
- * and they disagreed about `s.images({ ... }).remote()`: the Studio's version
11939
+ * and they disagreed about `s.imageset({ ... }).remote()`: the Studio's version
10973
11940
  * counted a remote media record, the server's did not, because a media
10974
11941
  * collection serializes as a `record` of metadata rather than as an image
10975
11942
  * schema.
@@ -11009,7 +11976,7 @@ function hasRemoteFileSchema(schema) {
11009
11976
  return hasRemoteFileSchema(schema.item);
11010
11977
  } else if (schema.type === "record") {
11011
11978
  /**
11012
- * A media collection — `s.images()` / `s.files()` — is the case both of the
11979
+ * A media collection — `s.imageset()` / `s.fileset()` — is the case both of the
11013
11980
  * old functions were written around, and the one they disagreed on.
11014
11981
  *
11015
11982
  * It has no file or image schema inside it to find: `item` is an object of
@@ -11051,7 +12018,7 @@ function hasRemoteFileSchema(schema) {
11051
12018
  return false;
11052
12019
  } else if (schema.type === "boolean" ||
11053
12020
  // An enum's values are strings, so there is nothing to look inside.
11054
- 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") {
12021
+ 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") {
11055
12022
  return false;
11056
12023
  } else {
11057
12024
  var exhaustiveCheck = schema;
@@ -11069,8 +12036,8 @@ function hasRemoteFileSchema(schema) {
11069
12036
  *
11070
12037
  * It is a walk over the SERIALIZED schema for the reasons given above, and for
11071
12038
  * one more that is specific to this question: the two cases most likely to be
11072
- * missed are invisible to a value type. A media collection (`s.images()` /
11073
- * `s.files()`) serializes as a record of metadata with no image schema inside
12039
+ * missed are invisible to a value type. A media collection (`s.imageset()` /
12040
+ * `s.fileset()`) serializes as a record of metadata with no image schema inside
11074
12041
  * it, and a richtext inline image lives in a constructor argument. A check
11075
12042
  * written against the item TYPE would silently pass both — and the gallery case
11076
12043
  * already has a data-loss incident attached to it (see above).
@@ -11123,7 +12090,7 @@ function hasMediaSchema(schema) {
11123
12090
  return false;
11124
12091
  } else if (schema.type === "boolean" ||
11125
12092
  // An enum's values are strings, so there is nothing to look inside.
11126
- 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" ||
12093
+ 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" ||
11127
12094
  // A settings module holds the assistant's configuration, not content, and
11128
12095
  // nothing in it is media.
11129
12096
  schema.type === "settings") {
@@ -11402,6 +12369,46 @@ function getFileHash(text) {
11402
12369
  return hashToRemoteFileHash(getSHA256Hash(new Uint8Array(text)));
11403
12370
  }
11404
12371
 
12372
+ /**
12373
+ * The variant of a discriminated union that a value takes, by its tag.
12374
+ *
12375
+ * A discriminated union is a fork rather than a level: the variant IS the node,
12376
+ * and everything a caller wants to know about that node — its fields, its
12377
+ * `locale` field, the schema to draw it with — is on the variant and not on the
12378
+ * union. Resolving it takes the source, so it cannot be done from the schema
12379
+ * alone, which is why every caller that has only the union ends up needing this.
12380
+ *
12381
+ * `undefined` where the tag matches nothing, rather than a fallback to the first
12382
+ * variant: a value whose tag names no variant is not a member of the union,
12383
+ * validation is already saying so, and reading fields out of the wrong shape
12384
+ * would answer confidently and wrongly.
12385
+ *
12386
+ * `s.enum()` never reaches here — it is a leaf whose values are strings, with no
12387
+ * variants to resolve — so anything that is not a discriminated union answers
12388
+ * `undefined` too.
12389
+ */
12390
+ function discriminatedUnionBranchOf(schema, tag) {
12391
+ if ((schema === null || schema === void 0 ? void 0 : schema.type) !== "discriminated-union") {
12392
+ return undefined;
12393
+ }
12394
+ var _iterator = result._createForOfIteratorHelper(schema.items),
12395
+ _step;
12396
+ try {
12397
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
12398
+ var item = _step.value;
12399
+ var discriminator = item.items[schema.key];
12400
+ if ((discriminator === null || discriminator === void 0 ? void 0 : discriminator.type) === "literal" && discriminator.value === tag) {
12401
+ return item;
12402
+ }
12403
+ }
12404
+ } catch (err) {
12405
+ _iterator.e(err);
12406
+ } finally {
12407
+ _iterator.f();
12408
+ }
12409
+ return undefined;
12410
+ }
12411
+
11405
12412
  function deserializeSchema(serialized) {
11406
12413
  var schema = deserializeSchemaImpl(serialized);
11407
12414
  if (serialized.readonly) {
@@ -11413,7 +12420,7 @@ function deserializeSchema(serialized) {
11413
12420
  return schema;
11414
12421
  }
11415
12422
  function deserializeSchemaImpl(serialized) {
11416
- 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$render11, _serialized$render12, _serialized$render13, _serialized$render14, _serialized$render15, _serialized$render16;
12423
+ 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$dir, _serialized$remote, _serialized$jsonValue, _serialized$render0, _serialized$external, _serialized$render1, _serialized$render12, _serialized$render13, _serialized$render14, _serialized$render15, _serialized$render16, _serialized$render17;
11417
12424
  switch (serialized.type) {
11418
12425
  case "string":
11419
12426
  return new StringSchema(_objectSpread2(_objectSpread2({}, serialized.options), {}, {
@@ -11442,7 +12449,12 @@ function deserializeSchemaImpl(serialized) {
11442
12449
  key = _ref4[0],
11443
12450
  item = _ref4[1];
11444
12451
  return [key, deserializeSchema(item)];
11445
- })), serialized.opt, false, false, serialized.description);
12452
+ })), serialized.opt, false, false, serialized.description,
12453
+ // Carried through, or the Studio's validation worker — which only ever
12454
+ // sees the deserialized schema — would run every field's own rules and
12455
+ // none of the section's, so a language declared twice would be reported
12456
+ // by the CLI and silently accepted in the Studio.
12457
+ serialized.section);
11446
12458
  case "array":
11447
12459
  return new ArraySchema(deserializeSchema(serialized.item), serialized.opt, [], false, false, serialized.description,
11448
12460
  // The preview closure cannot survive serialization; the render can.
@@ -11468,7 +12480,7 @@ function deserializeSchemaImpl(serialized) {
11468
12480
  return new RecordSchema(deserializeSchema(serialized.item), serialized.opt, [], null, serialized.key ? deserializeSchema(serialized.key) : null, serialized.mediaType ? {
11469
12481
  type: serialized.mediaType,
11470
12482
  accept: (_serialized$accept = serialized.accept) !== null && _serialized$accept !== void 0 ? _serialized$accept : "*/*",
11471
- directory: (_serialized$directory = serialized.directory) !== null && _serialized$directory !== void 0 ? _serialized$directory : "/public/val",
12483
+ dir: (_serialized$dir = serialized.dir) !== null && _serialized$dir !== void 0 ? _serialized$dir : "/public/val",
11472
12484
  remote: (_serialized$remote = serialized.remote) !== null && _serialized$remote !== void 0 ? _serialized$remote : false,
11473
12485
  altSchema: serialized.alt ? deserializeSchema(serialized.alt) : undefined
11474
12486
  } : undefined, false, false, serialized.description, (_serialized$jsonValue = serialized.jsonValues) !== null && _serialized$jsonValue !== void 0 ? _serialized$jsonValue : false,
@@ -11479,27 +12491,32 @@ function deserializeSchemaImpl(serialized) {
11479
12491
  (_serialized$external = serialized.external) !== null && _serialized$external !== void 0 ? _serialized$external : null);
11480
12492
  case "keyOf":
11481
12493
  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);
11482
- case "route":
12494
+ case "locale":
11483
12495
  {
11484
12496
  var _serialized$render10;
12497
+ return new LocaleSchema(serialized.opt, [], false, false, serialized.description, (_serialized$render10 = serialized.render) !== null && _serialized$render10 !== void 0 ? _serialized$render10 : null);
12498
+ }
12499
+ case "route":
12500
+ {
12501
+ var _serialized$render11;
11485
12502
  var routeOptions = serialized.options ? {
11486
12503
  include: serialized.options.include ? new RegExp(serialized.options.include.source, serialized.options.include.flags) : undefined,
11487
12504
  exclude: serialized.options.exclude ? new RegExp(serialized.options.exclude.source, serialized.options.exclude.flags) : undefined
11488
12505
  } : undefined;
11489
- return new RouteSchema(routeOptions, serialized.opt, [], false, false, serialized.description, (_serialized$render10 = serialized.render) !== null && _serialized$render10 !== void 0 ? _serialized$render10 : null);
12506
+ return new RouteSchema(routeOptions, serialized.opt, [], false, false, serialized.description, (_serialized$render11 = serialized.render) !== null && _serialized$render11 !== void 0 ? _serialized$render11 : null);
11490
12507
  }
11491
12508
  case "file":
11492
- return new FileSchema(serialized.options, serialized.opt, serialized.remote, [], {}, false, false, serialized.description, (_serialized$render11 = serialized.render) !== null && _serialized$render11 !== void 0 ? _serialized$render11 : null);
12509
+ 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);
11493
12510
  case "image":
11494
- return new ImageSchema(serialized.options, serialized.opt, serialized.remote, [], {}, false, false, serialized.description, (_serialized$render12 = serialized.render) !== null && _serialized$render12 !== void 0 ? _serialized$render12 : null);
12511
+ 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);
11495
12512
  case "date":
11496
- return new DateSchema(serialized.options, serialized.opt, [], false, false, serialized.description, (_serialized$render13 = serialized.render) !== null && _serialized$render13 !== void 0 ? _serialized$render13 : null);
12513
+ return new DateSchema(serialized.options, serialized.opt, [], false, false, serialized.description, (_serialized$render14 = serialized.render) !== null && _serialized$render14 !== void 0 ? _serialized$render14 : null);
11497
12514
  case "dateTime":
11498
- return new DateTimeSchema(serialized.options, serialized.opt, [], false, false, serialized.description, (_serialized$render14 = serialized.render) !== null && _serialized$render14 !== void 0 ? _serialized$render14 : null);
12515
+ return new DateTimeSchema(serialized.options, serialized.opt, [], false, false, serialized.description, (_serialized$render15 = serialized.render) !== null && _serialized$render15 !== void 0 ? _serialized$render15 : null);
11499
12516
  case "color":
11500
- return new ColorSchema(serialized.options, serialized.opt, [], false, false, serialized.description, (_serialized$render15 = serialized.render) !== null && _serialized$render15 !== void 0 ? _serialized$render15 : null);
12517
+ return new ColorSchema(serialized.options, serialized.opt, [], false, false, serialized.description, (_serialized$render16 = serialized.render) !== null && _serialized$render16 !== void 0 ? _serialized$render16 : null);
11501
12518
  case "code":
11502
- return new CodeSchema(serialized.options, serialized.opt, [], false, false, serialized.description, (_serialized$render16 = serialized.render) !== null && _serialized$render16 !== void 0 ? _serialized$render16 : null);
12519
+ return new CodeSchema(serialized.options, serialized.opt, [], false, false, serialized.description, (_serialized$render17 = serialized.render) !== null && _serialized$render17 !== void 0 ? _serialized$render17 : null);
11503
12520
  default:
11504
12521
  {
11505
12522
  var exhaustiveCheck = serialized;
@@ -12166,7 +13183,7 @@ function isPageRouter(routerId) {
12166
13183
 
12167
13184
  var corePackageJson = {
12168
13185
  name: "@valbuild/core",
12169
- version: "0.126.0",
13186
+ version: "0.128.0",
12170
13187
  "private": false,
12171
13188
  description: "Val - supercharged hard-coded content",
12172
13189
  repository: {
@@ -12359,6 +13376,7 @@ exports.ImageSchema = ImageSchema;
12359
13376
  exports.Internal = Internal;
12360
13377
  exports.KeyOfSchema = KeyOfSchema;
12361
13378
  exports.LiteralSchema = LiteralSchema;
13379
+ exports.LocaleSchema = LocaleSchema;
12362
13380
  exports.ModuleFilePathSep = ModuleFilePathSep;
12363
13381
  exports.NumberSchema = NumberSchema;
12364
13382
  exports.ObjectSchema = ObjectSchema;
@@ -12378,8 +13396,10 @@ exports._toConsumableArray = _toConsumableArray;
12378
13396
  exports._typeof = _typeof;
12379
13397
  exports.assistantAvailability = assistantAvailability;
12380
13398
  exports.computeValModuleShas = computeValModuleShas;
13399
+ exports.declaredKeySetOf = declaredKeySetOf;
12381
13400
  exports.derefPatch = derefPatch;
12382
13401
  exports.deserializeSchema = deserializeSchema;
13402
+ exports.discriminatedUnionBranchOf = discriminatedUnionBranchOf;
12383
13403
  exports.extractValModules = extractValModules;
12384
13404
  exports.getSourcePathFromRoute = getSourcePathFromRoute;
12385
13405
  exports.hasMediaSchema = hasMediaSchema;
@@ -12388,6 +13408,9 @@ exports.initVal = initVal;
12388
13408
  exports.isInlineRender = isInlineRender;
12389
13409
  exports.isPageRouter = isPageRouter;
12390
13410
  exports.isRootModuleFilePath = isRootModuleFilePath;
13411
+ exports.localeOfValue = localeOfValue;
13412
+ exports.localeTagError = localeTagError;
13413
+ exports.missingDeclaredKeys = missingDeclaredKeys;
12391
13414
  exports.modules = modules;
12392
13415
  exports.parseNextJsRoutePattern = parseNextJsRoutePattern;
12393
13416
  exports.parseTanStackRoutePattern = parseTanStackRoutePattern;