@valbuild/core 0.97.1 → 0.97.7

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.
@@ -285,7 +285,7 @@ export declare function initSchema(): {
285
285
  datetime: (options?: Record<string, never>) => import("./schema/datetime.js").DateTimeSchema<import("./schema/string.js").RawString>;
286
286
  route: <T extends string>(options?: Record<string, never>) => import("./schema/route.js").RouteSchema<T>;
287
287
  router: typeof router;
288
- images: <Accept extends `image/${string}`>(options: import("./schema/images.js").ImagesOptions<Accept>) => import("./schema/record.js").RecordSchema<import("./schema/object.js").ObjectSchema<{
288
+ images: <Accept extends `image/${string}`>(options?: import("./schema/images.js").ImagesOptions<Accept>) => import("./schema/record.js").RecordSchema<import("./schema/object.js").ObjectSchema<{
289
289
  width: import("./schema/number.js").NumberSchema<number>;
290
290
  height: import("./schema/number.js").NumberSchema<number>;
291
291
  mimeType: import("./schema/string.js").StringSchema<string>;
@@ -13,7 +13,7 @@ export type ContentConstructor = {
13
13
  export type ValConstructor = {
14
14
  unstable_getPath: typeof getPath;
15
15
  };
16
- export type ConfigDirectory = `/public/val`;
16
+ export type ConfigDirectory = `/public` | `/public/${string}`;
17
17
  export type ValConfig = {
18
18
  project?: string;
19
19
  root?: string;
@@ -6,7 +6,7 @@ export declare function splitRemoteRef(ref: string): {
6
6
  version: string;
7
7
  validationHash: string;
8
8
  fileHash: string;
9
- filePath: `public/val/${string}`;
9
+ filePath: `public/${string}`;
10
10
  } | {
11
11
  status: "error";
12
12
  error: string;
@@ -14,8 +14,9 @@ export type AltSchema = StringSchema<string> | StringSchema<string | null> | Rec
14
14
  export type ImagesOptions<Accept extends `image/${string}`> = {
15
15
  /**
16
16
  * The accepted mime type pattern. Must be an image type (e.g., "image/png", "image/webp", "image/*")
17
+ * @default "image/*"
17
18
  */
18
- accept: Accept;
19
+ accept?: Accept;
19
20
  /**
20
21
  * The directory where images should be stored.
21
22
  * Must start with "/public" (e.g., "/public/val/images")
@@ -64,6 +65,9 @@ type ImagesItemSrc = {
64
65
  /**
65
66
  * Define a collection of images.
66
67
  *
68
+ * All options are optional: `s.images()` accepts any image type (`"image/*"`) in
69
+ * the default `/public/val` directory, with nullable alt text and remote disabled.
70
+ *
67
71
  * @example
68
72
  * ```typescript
69
73
  * const schema = s.images({
@@ -81,5 +85,5 @@ type ImagesItemSrc = {
81
85
  * });
82
86
  * ```
83
87
  */
84
- export declare const images: <Accept extends `image/${string}`>(options: ImagesOptions<Accept>) => RecordSchema<ObjectSchema<ImagesItemProps, ImagesItemSrc>, Schema<string>, Record<string, ImagesEntryMetadata>>;
88
+ export declare const images: <Accept extends `image/${string}`>(options?: ImagesOptions<Accept>) => RecordSchema<ObjectSchema<ImagesItemProps, ImagesItemSrc>, Schema<string>, Record<string, ImagesEntryMetadata>>;
85
89
  export {};
@@ -86,6 +86,15 @@ export declare abstract class Schema<Src extends SelectorSource> {
86
86
  protected abstract executeRender(sourcePath: SourcePath | ModuleFilePath, src: Src): ReifiedRender;
87
87
  /** MUTATES! since internal and perf sensitive */
88
88
  protected appendValidationError(current: ValidationErrors, path: SourcePath, message: string, value: unknown, schemaError?: boolean): ValidationErrors;
89
+ /**
90
+ * Merges two sets of validation errors path-wise: errors on the same path are
91
+ * concatenated, not overwritten. Object spread cannot be used for this, since
92
+ * it replaces the array of the colliding path. A record, for example, validates
93
+ * the key and the item on the same path, so both sets must survive.
94
+ *
95
+ * MUTATES! since internal and perf sensitive
96
+ */
97
+ protected mergeValidationErrors(current: ValidationErrors, incoming: ValidationErrors): ValidationErrors;
89
98
  }
90
99
  export type SelectorOfSchema<T extends Schema<SelectorSource>> = T extends Schema<infer Src> ? Src : never;
91
100
  export {};
@@ -45,6 +45,8 @@ export declare class RecordSchema<T extends Schema<SelectorSource>, K extends Sc
45
45
  validate(validationFunction: (src: Src) => false | string): RecordSchema<T, K, Src>;
46
46
  protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
47
47
  private isRemoteUrl;
48
+ /** Marks the validation errors reported at `path` as key errors (in place). */
49
+ private markKeyErrorsAtPath;
48
50
  private validateMediaKey;
49
51
  private validateMediaEntry;
50
52
  private validateMediaMimeType;
@@ -2,4 +2,5 @@ import { Schema, SelectorOfSchema } from "./index.js";
2
2
  import { ValRouter } from "../router.js";
3
3
  import { SelectorSource } from "../selector/index.js";
4
4
  import { RecordSchema } from "./record.js";
5
- export declare function router<T extends Schema<SelectorSource>, Src extends Record<string, SelectorOfSchema<T>>>(router: ValRouter, item: T): RecordSchema<T, Schema<string>, Src>;
5
+ export declare function router<K extends Schema<string>, T extends Schema<SelectorSource>>(router: ValRouter, key: K, item: T): RecordSchema<T, K, Record<SelectorOfSchema<K>, SelectorOfSchema<T>>>;
6
+ export declare function router<T extends Schema<SelectorSource>>(router: ValRouter, item: T): RecordSchema<T, Schema<string>, Record<string, SelectorOfSchema<T>>>;
@@ -5,6 +5,6 @@ export type FileSelector<Metadata extends FileMetadata | undefined> = GenericSel
5
5
  url: string;
6
6
  }> & {
7
7
  readonly url: UnknownSelector<string>;
8
- } & Metadata extends undefined ? {} : Metadata extends Source ? {
8
+ } & (Metadata extends undefined ? {} : Metadata extends Source ? {
9
9
  readonly metadata: UnknownSelector<Metadata>;
10
- } : {};
10
+ } : {});
@@ -22,7 +22,7 @@ export type FileSource<Metadata extends FileMetadata | undefined = FileMetadata
22
22
  export declare const initFile: (config?: ValConfig) => {
23
23
  <Metadata extends {
24
24
  readonly [key: string]: Json;
25
- }>(ref: `${"/public/val"}/${string}`, metadata: Metadata): FileSource<Metadata>;
26
- (ref: `${"/public/val"}/${string}`, metadata?: undefined): FileSource<undefined>;
25
+ }>(ref: `${import("../initVal.js").ConfigDirectory}/${string}`, metadata: Metadata): FileSource<Metadata>;
26
+ (ref: `${import("../initVal.js").ConfigDirectory}/${string}`, metadata?: undefined): FileSource<undefined>;
27
27
  };
28
28
  export declare function isFile(obj: unknown): obj is FileSource;
@@ -14,6 +14,6 @@ export type ImageSource<Metadata extends ImageMetadata | undefined = ImageMetada
14
14
  readonly patch_id?: string;
15
15
  };
16
16
  export declare const initImage: (config?: ValConfig) => {
17
- (ref: `${"/public/val"}/${string}`, metadata: ImageMetadata): ImageSource<ImageMetadata>;
18
- (ref: `${"/public/val"}/${string}`, metadata?: undefined): ImageSource<undefined>;
17
+ (ref: `${import("../initVal.js").ConfigDirectory}/${string}`, metadata: ImageMetadata): ImageSource<ImageMetadata>;
18
+ (ref: `${import("../initVal.js").ConfigDirectory}/${string}`, metadata?: undefined): ImageSource<undefined>;
19
19
  };
@@ -12,12 +12,12 @@ export type RemoteSource<Metadata extends FileMetadata | undefined = FileMetadat
12
12
  readonly patch_id?: string;
13
13
  };
14
14
  export declare const initRemote: (config?: ValConfig) => <Metadata extends FileMetadata | ImageMetadata>(ref: RemoteRef, metadata?: Metadata) => RemoteSource<Metadata>;
15
- export type RemoteRef = `${string}/file/p/${string}/v/${string}/h/${string}/f/${string}/p/public/val/${string}`;
15
+ export type RemoteRef = `${string}/file/p/${string}/b/${string}/v/${string}/h/${string}/f/${string}/p/public/${string}`;
16
16
  export declare function createRemoteRef(remoteHost: string, { publicProjectId, coreVersion, validationHash, fileHash, filePath, bucket, }: {
17
17
  publicProjectId: string;
18
18
  coreVersion: string;
19
19
  validationHash: string;
20
20
  fileHash: string;
21
- filePath: `public/val/${string}`;
21
+ filePath: `public/${string}`;
22
22
  bucket: string;
23
23
  }): RemoteRef;
@@ -222,6 +222,34 @@ var Schema = /*#__PURE__*/function () {
222
222
  }]);
223
223
  }
224
224
  }
225
+
226
+ /**
227
+ * Merges two sets of validation errors path-wise: errors on the same path are
228
+ * concatenated, not overwritten. Object spread cannot be used for this, since
229
+ * it replaces the array of the colliding path. A record, for example, validates
230
+ * the key and the item on the same path, so both sets must survive.
231
+ *
232
+ * MUTATES! since internal and perf sensitive
233
+ */
234
+ }, {
235
+ key: "mergeValidationErrors",
236
+ value: function mergeValidationErrors(current, incoming) {
237
+ if (!incoming) {
238
+ return current;
239
+ }
240
+ if (!current) {
241
+ return incoming;
242
+ }
243
+ for (var pathS in incoming) {
244
+ var _path = pathS;
245
+ if (current[_path]) {
246
+ current[_path] = current[_path].concat(incoming[_path]);
247
+ } else {
248
+ current[_path] = incoming[_path];
249
+ }
250
+ }
251
+ return current;
252
+ }
225
253
  }]);
226
254
  }();
227
255
 
@@ -1007,11 +1035,7 @@ var ObjectSchema = /*#__PURE__*/function (_Schema) {
1007
1035
  src);
1008
1036
  } else {
1009
1037
  var subError = schema["executeValidate"](subPath, src[_key]);
1010
- if (subError && error) {
1011
- error = _objectSpread2(_objectSpread2({}, subError), error);
1012
- } else if (subError) {
1013
- error = subError;
1014
- }
1038
+ error = this.mergeValidationErrors(error, subError);
1015
1039
  }
1016
1040
  }
1017
1041
  return error;
@@ -1185,7 +1209,7 @@ var ArraySchema = /*#__PURE__*/function (_Schema) {
1185
1209
  if (assertRes.data === null) {
1186
1210
  return false;
1187
1211
  }
1188
- var error = {};
1212
+ var error = false;
1189
1213
  for (var _i = 0, _Object$entries = Object.entries(assertRes.data); _i < _Object$entries.length; _i++) {
1190
1214
  var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
1191
1215
  idx = _Object$entries$_i[0],
@@ -1193,12 +1217,7 @@ var ArraySchema = /*#__PURE__*/function (_Schema) {
1193
1217
  var subPath = unsafeCreateSourcePath(path, Number(idx));
1194
1218
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1195
1219
  var subError = this.item["executeValidate"](subPath, i);
1196
- if (subError) {
1197
- error = _objectSpread2(_objectSpread2({}, subError), error);
1198
- }
1199
- }
1200
- if (Object.keys(error).length === 0) {
1201
- return false;
1220
+ error = this.mergeValidationErrors(error, subError);
1202
1221
  }
1203
1222
  return error;
1204
1223
  }
@@ -2892,7 +2911,15 @@ function splitRemoteRef(ref) {
2892
2911
  error: "Invalid remote ref: " + ref
2893
2912
  };
2894
2913
  }
2895
- if (match[7].indexOf("public/val/") !== 0) {
2914
+ if (match[7].indexOf("public/") !== 0) {
2915
+ return {
2916
+ status: "error",
2917
+ error: "Invalid remote ref: " + ref
2918
+ };
2919
+ }
2920
+ if (match[7].split("/").some(function (segment) {
2921
+ return segment === "." || segment === "..";
2922
+ })) {
2896
2923
  return {
2897
2924
  status: "error",
2898
2925
  error: "Invalid remote ref: " + ref
@@ -2973,9 +3000,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
2973
3000
  }]));
2974
3001
  }
2975
3002
  var routerValidations = this.getRouterValidations(path, src);
2976
- if (routerValidations) {
2977
- return routerValidations;
2978
- }
3003
+ error = this.mergeValidationErrors(error, routerValidations);
2979
3004
  var _iterator = result._createForOfIteratorHelper(customValidationErrors),
2980
3005
  _step;
2981
3006
  try {
@@ -3039,15 +3064,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
3039
3064
  keyError: true
3040
3065
  });
3041
3066
  });
3042
- if (error) {
3043
- if (error[keyPath]) {
3044
- error[keyPath] = [].concat(_toConsumableArray(error[keyPath]), _toConsumableArray(keyError[keyPath]));
3045
- } else {
3046
- error[keyPath] = keyError[keyPath];
3047
- }
3048
- } else {
3049
- error = keyError;
3050
- }
3067
+ error = _this2.mergeValidationErrors(error, keyError);
3051
3068
  }
3052
3069
  }
3053
3070
  var subPath = createValPathOfItem(path, key);
@@ -3056,22 +3073,22 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
3056
3073
  // Should! never happen
3057
3074
  src);
3058
3075
  } else if (_this2.mediaOptions) {
3059
- // Media collection: validate key (path/URL) and entry (metadata)
3076
+ // Media collection: validate key (path/URL) and entry (metadata).
3077
+ // Gallery entries are keyed by their file path and the metadata is
3078
+ // derived, so surface entry errors on the key rather than the value.
3060
3079
  var keyErr = _this2.validateMediaKey(subPath, key);
3061
3080
  if (keyErr) {
3062
- error = error ? _objectSpread2(_objectSpread2({}, error), keyErr) : keyErr;
3081
+ _this2.markKeyErrorsAtPath(keyErr, subPath);
3063
3082
  }
3083
+ error = _this2.mergeValidationErrors(error, keyErr);
3064
3084
  var entryErr = _this2.validateMediaEntry(subPath, elem);
3065
3085
  if (entryErr) {
3066
- error = error ? _objectSpread2(_objectSpread2({}, error), entryErr) : entryErr;
3086
+ _this2.markKeyErrorsAtPath(entryErr, subPath);
3067
3087
  }
3088
+ error = _this2.mergeValidationErrors(error, entryErr);
3068
3089
  } else {
3069
3090
  var subError = _this2.item["executeValidate"](subPath, elem);
3070
- if (subError && error) {
3071
- error = _objectSpread2(_objectSpread2({}, subError), error);
3072
- } else if (subError) {
3073
- error = subError;
3074
- }
3091
+ error = _this2.mergeValidationErrors(error, subError);
3075
3092
  }
3076
3093
  });
3077
3094
  return error;
@@ -3081,6 +3098,19 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
3081
3098
  value: function isRemoteUrl(url) {
3082
3099
  return url.startsWith("https://") || url.startsWith("http://");
3083
3100
  }
3101
+
3102
+ /** Marks the validation errors reported at `path` as key errors (in place). */
3103
+ }, {
3104
+ key: "markKeyErrorsAtPath",
3105
+ value: function markKeyErrorsAtPath(errors, path) {
3106
+ if (errors && errors[path]) {
3107
+ errors[path] = errors[path].map(function (err) {
3108
+ return _objectSpread2(_objectSpread2({}, err), {}, {
3109
+ keyError: true
3110
+ });
3111
+ });
3112
+ }
3113
+ }
3084
3114
  }, {
3085
3115
  key: "validateMediaKey",
3086
3116
  value: function validateMediaKey(path, key) {
@@ -4882,10 +4912,11 @@ var datetime = function datetime(options) {
4882
4912
  return new DateTimeSchema(options);
4883
4913
  };
4884
4914
 
4885
- function router(router, item) {
4886
- var keySchema = string();
4887
- var recordSchema = new RecordSchema(item, false, [], router, keySchema);
4888
- return recordSchema;
4915
+ function router(router, keyOrItem, maybeItem) {
4916
+ if (maybeItem) {
4917
+ return new RecordSchema(maybeItem, false, [], router, keyOrItem);
4918
+ }
4919
+ return new RecordSchema(keyOrItem, false, [], router, string());
4889
4920
  }
4890
4921
 
4891
4922
  /**
@@ -4905,6 +4936,9 @@ function router(router, item) {
4905
4936
  /**
4906
4937
  * Define a collection of images.
4907
4938
  *
4939
+ * All options are optional: `s.images()` accepts any image type (`"image/*"`) in
4940
+ * the default `/public/val` directory, with nullable alt text and remote disabled.
4941
+ *
4908
4942
  * @example
4909
4943
  * ```typescript
4910
4944
  * const schema = s.images({
@@ -4923,9 +4957,9 @@ function router(router, item) {
4923
4957
  * ```
4924
4958
  */
4925
4959
  var images = function images(options) {
4926
- var _options$directory, _options$alt, _options$remote;
4927
- var directory = (_options$directory = options.directory) !== null && _options$directory !== void 0 ? _options$directory : "/public/val";
4928
- var altSchema = (_options$alt = options.alt) !== null && _options$alt !== void 0 ? _options$alt : string().nullable();
4960
+ var _options$directory, _options$alt, _options$accept, _options$remote;
4961
+ var directory = (_options$directory = options === null || options === void 0 ? void 0 : options.directory) !== null && _options$directory !== void 0 ? _options$directory : "/public/val";
4962
+ var altSchema = (_options$alt = options === null || options === void 0 ? void 0 : options.alt) !== null && _options$alt !== void 0 ? _options$alt : string().nullable();
4929
4963
  var itemSchema = new ObjectSchema({
4930
4964
  width: new NumberSchema(undefined, false),
4931
4965
  height: new NumberSchema(undefined, false),
@@ -4934,9 +4968,9 @@ var images = function images(options) {
4934
4968
  }, false);
4935
4969
  return new RecordSchema(itemSchema, false, [], null, null, {
4936
4970
  type: "images",
4937
- accept: options.accept,
4971
+ accept: (_options$accept = options === null || options === void 0 ? void 0 : options.accept) !== null && _options$accept !== void 0 ? _options$accept : "image/*",
4938
4972
  directory: directory,
4939
- remote: (_options$remote = options.remote) !== null && _options$remote !== void 0 ? _options$remote : false,
4973
+ remote: (_options$remote = options === null || options === void 0 ? void 0 : options.remote) !== null && _options$remote !== void 0 ? _options$remote : false,
4940
4974
  altSchema: altSchema
4941
4975
  });
4942
4976
  };
@@ -5042,6 +5076,9 @@ var initRemote = function initRemote(config) {
5042
5076
  }
5043
5077
  return remote;
5044
5078
  };
5079
+
5080
+ // NOTE: the segments must match the ref built by createRemoteRef below and the RegEx in splitRemoteRef.
5081
+
5045
5082
  function createRemoteRef(remoteHost, _ref2) {
5046
5083
  var publicProjectId = _ref2.publicProjectId,
5047
5084
  coreVersion = _ref2.coreVersion,
@@ -222,6 +222,34 @@ var Schema = /*#__PURE__*/function () {
222
222
  }]);
223
223
  }
224
224
  }
225
+
226
+ /**
227
+ * Merges two sets of validation errors path-wise: errors on the same path are
228
+ * concatenated, not overwritten. Object spread cannot be used for this, since
229
+ * it replaces the array of the colliding path. A record, for example, validates
230
+ * the key and the item on the same path, so both sets must survive.
231
+ *
232
+ * MUTATES! since internal and perf sensitive
233
+ */
234
+ }, {
235
+ key: "mergeValidationErrors",
236
+ value: function mergeValidationErrors(current, incoming) {
237
+ if (!incoming) {
238
+ return current;
239
+ }
240
+ if (!current) {
241
+ return incoming;
242
+ }
243
+ for (var pathS in incoming) {
244
+ var _path = pathS;
245
+ if (current[_path]) {
246
+ current[_path] = current[_path].concat(incoming[_path]);
247
+ } else {
248
+ current[_path] = incoming[_path];
249
+ }
250
+ }
251
+ return current;
252
+ }
225
253
  }]);
226
254
  }();
227
255
 
@@ -1007,11 +1035,7 @@ var ObjectSchema = /*#__PURE__*/function (_Schema) {
1007
1035
  src);
1008
1036
  } else {
1009
1037
  var subError = schema["executeValidate"](subPath, src[_key]);
1010
- if (subError && error) {
1011
- error = _objectSpread2(_objectSpread2({}, subError), error);
1012
- } else if (subError) {
1013
- error = subError;
1014
- }
1038
+ error = this.mergeValidationErrors(error, subError);
1015
1039
  }
1016
1040
  }
1017
1041
  return error;
@@ -1185,7 +1209,7 @@ var ArraySchema = /*#__PURE__*/function (_Schema) {
1185
1209
  if (assertRes.data === null) {
1186
1210
  return false;
1187
1211
  }
1188
- var error = {};
1212
+ var error = false;
1189
1213
  for (var _i = 0, _Object$entries = Object.entries(assertRes.data); _i < _Object$entries.length; _i++) {
1190
1214
  var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
1191
1215
  idx = _Object$entries$_i[0],
@@ -1193,12 +1217,7 @@ var ArraySchema = /*#__PURE__*/function (_Schema) {
1193
1217
  var subPath = unsafeCreateSourcePath(path, Number(idx));
1194
1218
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1195
1219
  var subError = this.item["executeValidate"](subPath, i);
1196
- if (subError) {
1197
- error = _objectSpread2(_objectSpread2({}, subError), error);
1198
- }
1199
- }
1200
- if (Object.keys(error).length === 0) {
1201
- return false;
1220
+ error = this.mergeValidationErrors(error, subError);
1202
1221
  }
1203
1222
  return error;
1204
1223
  }
@@ -2892,7 +2911,15 @@ function splitRemoteRef(ref) {
2892
2911
  error: "Invalid remote ref: " + ref
2893
2912
  };
2894
2913
  }
2895
- if (match[7].indexOf("public/val/") !== 0) {
2914
+ if (match[7].indexOf("public/") !== 0) {
2915
+ return {
2916
+ status: "error",
2917
+ error: "Invalid remote ref: " + ref
2918
+ };
2919
+ }
2920
+ if (match[7].split("/").some(function (segment) {
2921
+ return segment === "." || segment === "..";
2922
+ })) {
2896
2923
  return {
2897
2924
  status: "error",
2898
2925
  error: "Invalid remote ref: " + ref
@@ -2973,9 +3000,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
2973
3000
  }]));
2974
3001
  }
2975
3002
  var routerValidations = this.getRouterValidations(path, src);
2976
- if (routerValidations) {
2977
- return routerValidations;
2978
- }
3003
+ error = this.mergeValidationErrors(error, routerValidations);
2979
3004
  var _iterator = result._createForOfIteratorHelper(customValidationErrors),
2980
3005
  _step;
2981
3006
  try {
@@ -3039,15 +3064,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
3039
3064
  keyError: true
3040
3065
  });
3041
3066
  });
3042
- if (error) {
3043
- if (error[keyPath]) {
3044
- error[keyPath] = [].concat(_toConsumableArray(error[keyPath]), _toConsumableArray(keyError[keyPath]));
3045
- } else {
3046
- error[keyPath] = keyError[keyPath];
3047
- }
3048
- } else {
3049
- error = keyError;
3050
- }
3067
+ error = _this2.mergeValidationErrors(error, keyError);
3051
3068
  }
3052
3069
  }
3053
3070
  var subPath = createValPathOfItem(path, key);
@@ -3056,22 +3073,22 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
3056
3073
  // Should! never happen
3057
3074
  src);
3058
3075
  } else if (_this2.mediaOptions) {
3059
- // Media collection: validate key (path/URL) and entry (metadata)
3076
+ // Media collection: validate key (path/URL) and entry (metadata).
3077
+ // Gallery entries are keyed by their file path and the metadata is
3078
+ // derived, so surface entry errors on the key rather than the value.
3060
3079
  var keyErr = _this2.validateMediaKey(subPath, key);
3061
3080
  if (keyErr) {
3062
- error = error ? _objectSpread2(_objectSpread2({}, error), keyErr) : keyErr;
3081
+ _this2.markKeyErrorsAtPath(keyErr, subPath);
3063
3082
  }
3083
+ error = _this2.mergeValidationErrors(error, keyErr);
3064
3084
  var entryErr = _this2.validateMediaEntry(subPath, elem);
3065
3085
  if (entryErr) {
3066
- error = error ? _objectSpread2(_objectSpread2({}, error), entryErr) : entryErr;
3086
+ _this2.markKeyErrorsAtPath(entryErr, subPath);
3067
3087
  }
3088
+ error = _this2.mergeValidationErrors(error, entryErr);
3068
3089
  } else {
3069
3090
  var subError = _this2.item["executeValidate"](subPath, elem);
3070
- if (subError && error) {
3071
- error = _objectSpread2(_objectSpread2({}, subError), error);
3072
- } else if (subError) {
3073
- error = subError;
3074
- }
3091
+ error = _this2.mergeValidationErrors(error, subError);
3075
3092
  }
3076
3093
  });
3077
3094
  return error;
@@ -3081,6 +3098,19 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
3081
3098
  value: function isRemoteUrl(url) {
3082
3099
  return url.startsWith("https://") || url.startsWith("http://");
3083
3100
  }
3101
+
3102
+ /** Marks the validation errors reported at `path` as key errors (in place). */
3103
+ }, {
3104
+ key: "markKeyErrorsAtPath",
3105
+ value: function markKeyErrorsAtPath(errors, path) {
3106
+ if (errors && errors[path]) {
3107
+ errors[path] = errors[path].map(function (err) {
3108
+ return _objectSpread2(_objectSpread2({}, err), {}, {
3109
+ keyError: true
3110
+ });
3111
+ });
3112
+ }
3113
+ }
3084
3114
  }, {
3085
3115
  key: "validateMediaKey",
3086
3116
  value: function validateMediaKey(path, key) {
@@ -4882,10 +4912,11 @@ var datetime = function datetime(options) {
4882
4912
  return new DateTimeSchema(options);
4883
4913
  };
4884
4914
 
4885
- function router(router, item) {
4886
- var keySchema = string();
4887
- var recordSchema = new RecordSchema(item, false, [], router, keySchema);
4888
- return recordSchema;
4915
+ function router(router, keyOrItem, maybeItem) {
4916
+ if (maybeItem) {
4917
+ return new RecordSchema(maybeItem, false, [], router, keyOrItem);
4918
+ }
4919
+ return new RecordSchema(keyOrItem, false, [], router, string());
4889
4920
  }
4890
4921
 
4891
4922
  /**
@@ -4905,6 +4936,9 @@ function router(router, item) {
4905
4936
  /**
4906
4937
  * Define a collection of images.
4907
4938
  *
4939
+ * All options are optional: `s.images()` accepts any image type (`"image/*"`) in
4940
+ * the default `/public/val` directory, with nullable alt text and remote disabled.
4941
+ *
4908
4942
  * @example
4909
4943
  * ```typescript
4910
4944
  * const schema = s.images({
@@ -4923,9 +4957,9 @@ function router(router, item) {
4923
4957
  * ```
4924
4958
  */
4925
4959
  var images = function images(options) {
4926
- var _options$directory, _options$alt, _options$remote;
4927
- var directory = (_options$directory = options.directory) !== null && _options$directory !== void 0 ? _options$directory : "/public/val";
4928
- var altSchema = (_options$alt = options.alt) !== null && _options$alt !== void 0 ? _options$alt : string().nullable();
4960
+ var _options$directory, _options$alt, _options$accept, _options$remote;
4961
+ var directory = (_options$directory = options === null || options === void 0 ? void 0 : options.directory) !== null && _options$directory !== void 0 ? _options$directory : "/public/val";
4962
+ var altSchema = (_options$alt = options === null || options === void 0 ? void 0 : options.alt) !== null && _options$alt !== void 0 ? _options$alt : string().nullable();
4929
4963
  var itemSchema = new ObjectSchema({
4930
4964
  width: new NumberSchema(undefined, false),
4931
4965
  height: new NumberSchema(undefined, false),
@@ -4934,9 +4968,9 @@ var images = function images(options) {
4934
4968
  }, false);
4935
4969
  return new RecordSchema(itemSchema, false, [], null, null, {
4936
4970
  type: "images",
4937
- accept: options.accept,
4971
+ accept: (_options$accept = options === null || options === void 0 ? void 0 : options.accept) !== null && _options$accept !== void 0 ? _options$accept : "image/*",
4938
4972
  directory: directory,
4939
- remote: (_options$remote = options.remote) !== null && _options$remote !== void 0 ? _options$remote : false,
4973
+ remote: (_options$remote = options === null || options === void 0 ? void 0 : options.remote) !== null && _options$remote !== void 0 ? _options$remote : false,
4940
4974
  altSchema: altSchema
4941
4975
  });
4942
4976
  };
@@ -5042,6 +5076,9 @@ var initRemote = function initRemote(config) {
5042
5076
  }
5043
5077
  return remote;
5044
5078
  };
5079
+
5080
+ // NOTE: the segments must match the ref built by createRemoteRef below and the RegEx in splitRemoteRef.
5081
+
5045
5082
  function createRemoteRef(remoteHost, _ref2) {
5046
5083
  var publicProjectId = _ref2.publicProjectId,
5047
5084
  coreVersion = _ref2.coreVersion,
@@ -220,6 +220,34 @@ var Schema = /*#__PURE__*/function () {
220
220
  }]);
221
221
  }
222
222
  }
223
+
224
+ /**
225
+ * Merges two sets of validation errors path-wise: errors on the same path are
226
+ * concatenated, not overwritten. Object spread cannot be used for this, since
227
+ * it replaces the array of the colliding path. A record, for example, validates
228
+ * the key and the item on the same path, so both sets must survive.
229
+ *
230
+ * MUTATES! since internal and perf sensitive
231
+ */
232
+ }, {
233
+ key: "mergeValidationErrors",
234
+ value: function mergeValidationErrors(current, incoming) {
235
+ if (!incoming) {
236
+ return current;
237
+ }
238
+ if (!current) {
239
+ return incoming;
240
+ }
241
+ for (var pathS in incoming) {
242
+ var _path = pathS;
243
+ if (current[_path]) {
244
+ current[_path] = current[_path].concat(incoming[_path]);
245
+ } else {
246
+ current[_path] = incoming[_path];
247
+ }
248
+ }
249
+ return current;
250
+ }
223
251
  }]);
224
252
  }();
225
253
 
@@ -1005,11 +1033,7 @@ var ObjectSchema = /*#__PURE__*/function (_Schema) {
1005
1033
  src);
1006
1034
  } else {
1007
1035
  var subError = schema["executeValidate"](subPath, src[_key]);
1008
- if (subError && error) {
1009
- error = _objectSpread2(_objectSpread2({}, subError), error);
1010
- } else if (subError) {
1011
- error = subError;
1012
- }
1036
+ error = this.mergeValidationErrors(error, subError);
1013
1037
  }
1014
1038
  }
1015
1039
  return error;
@@ -1183,7 +1207,7 @@ var ArraySchema = /*#__PURE__*/function (_Schema) {
1183
1207
  if (assertRes.data === null) {
1184
1208
  return false;
1185
1209
  }
1186
- var error = {};
1210
+ var error = false;
1187
1211
  for (var _i = 0, _Object$entries = Object.entries(assertRes.data); _i < _Object$entries.length; _i++) {
1188
1212
  var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
1189
1213
  idx = _Object$entries$_i[0],
@@ -1191,12 +1215,7 @@ var ArraySchema = /*#__PURE__*/function (_Schema) {
1191
1215
  var subPath = unsafeCreateSourcePath(path, Number(idx));
1192
1216
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1193
1217
  var subError = this.item["executeValidate"](subPath, i);
1194
- if (subError) {
1195
- error = _objectSpread2(_objectSpread2({}, subError), error);
1196
- }
1197
- }
1198
- if (Object.keys(error).length === 0) {
1199
- return false;
1218
+ error = this.mergeValidationErrors(error, subError);
1200
1219
  }
1201
1220
  return error;
1202
1221
  }
@@ -2890,7 +2909,15 @@ function splitRemoteRef(ref) {
2890
2909
  error: "Invalid remote ref: " + ref
2891
2910
  };
2892
2911
  }
2893
- if (match[7].indexOf("public/val/") !== 0) {
2912
+ if (match[7].indexOf("public/") !== 0) {
2913
+ return {
2914
+ status: "error",
2915
+ error: "Invalid remote ref: " + ref
2916
+ };
2917
+ }
2918
+ if (match[7].split("/").some(function (segment) {
2919
+ return segment === "." || segment === "..";
2920
+ })) {
2894
2921
  return {
2895
2922
  status: "error",
2896
2923
  error: "Invalid remote ref: " + ref
@@ -2971,9 +2998,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
2971
2998
  }]));
2972
2999
  }
2973
3000
  var routerValidations = this.getRouterValidations(path, src);
2974
- if (routerValidations) {
2975
- return routerValidations;
2976
- }
3001
+ error = this.mergeValidationErrors(error, routerValidations);
2977
3002
  var _iterator = _createForOfIteratorHelper(customValidationErrors),
2978
3003
  _step;
2979
3004
  try {
@@ -3037,15 +3062,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
3037
3062
  keyError: true
3038
3063
  });
3039
3064
  });
3040
- if (error) {
3041
- if (error[keyPath]) {
3042
- error[keyPath] = [].concat(_toConsumableArray(error[keyPath]), _toConsumableArray(keyError[keyPath]));
3043
- } else {
3044
- error[keyPath] = keyError[keyPath];
3045
- }
3046
- } else {
3047
- error = keyError;
3048
- }
3065
+ error = _this2.mergeValidationErrors(error, keyError);
3049
3066
  }
3050
3067
  }
3051
3068
  var subPath = createValPathOfItem(path, key);
@@ -3054,22 +3071,22 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
3054
3071
  // Should! never happen
3055
3072
  src);
3056
3073
  } else if (_this2.mediaOptions) {
3057
- // Media collection: validate key (path/URL) and entry (metadata)
3074
+ // Media collection: validate key (path/URL) and entry (metadata).
3075
+ // Gallery entries are keyed by their file path and the metadata is
3076
+ // derived, so surface entry errors on the key rather than the value.
3058
3077
  var keyErr = _this2.validateMediaKey(subPath, key);
3059
3078
  if (keyErr) {
3060
- error = error ? _objectSpread2(_objectSpread2({}, error), keyErr) : keyErr;
3079
+ _this2.markKeyErrorsAtPath(keyErr, subPath);
3061
3080
  }
3081
+ error = _this2.mergeValidationErrors(error, keyErr);
3062
3082
  var entryErr = _this2.validateMediaEntry(subPath, elem);
3063
3083
  if (entryErr) {
3064
- error = error ? _objectSpread2(_objectSpread2({}, error), entryErr) : entryErr;
3084
+ _this2.markKeyErrorsAtPath(entryErr, subPath);
3065
3085
  }
3086
+ error = _this2.mergeValidationErrors(error, entryErr);
3066
3087
  } else {
3067
3088
  var subError = _this2.item["executeValidate"](subPath, elem);
3068
- if (subError && error) {
3069
- error = _objectSpread2(_objectSpread2({}, subError), error);
3070
- } else if (subError) {
3071
- error = subError;
3072
- }
3089
+ error = _this2.mergeValidationErrors(error, subError);
3073
3090
  }
3074
3091
  });
3075
3092
  return error;
@@ -3079,6 +3096,19 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
3079
3096
  value: function isRemoteUrl(url) {
3080
3097
  return url.startsWith("https://") || url.startsWith("http://");
3081
3098
  }
3099
+
3100
+ /** Marks the validation errors reported at `path` as key errors (in place). */
3101
+ }, {
3102
+ key: "markKeyErrorsAtPath",
3103
+ value: function markKeyErrorsAtPath(errors, path) {
3104
+ if (errors && errors[path]) {
3105
+ errors[path] = errors[path].map(function (err) {
3106
+ return _objectSpread2(_objectSpread2({}, err), {}, {
3107
+ keyError: true
3108
+ });
3109
+ });
3110
+ }
3111
+ }
3082
3112
  }, {
3083
3113
  key: "validateMediaKey",
3084
3114
  value: function validateMediaKey(path, key) {
@@ -4880,10 +4910,11 @@ var datetime = function datetime(options) {
4880
4910
  return new DateTimeSchema(options);
4881
4911
  };
4882
4912
 
4883
- function router(router, item) {
4884
- var keySchema = string();
4885
- var recordSchema = new RecordSchema(item, false, [], router, keySchema);
4886
- return recordSchema;
4913
+ function router(router, keyOrItem, maybeItem) {
4914
+ if (maybeItem) {
4915
+ return new RecordSchema(maybeItem, false, [], router, keyOrItem);
4916
+ }
4917
+ return new RecordSchema(keyOrItem, false, [], router, string());
4887
4918
  }
4888
4919
 
4889
4920
  /**
@@ -4903,6 +4934,9 @@ function router(router, item) {
4903
4934
  /**
4904
4935
  * Define a collection of images.
4905
4936
  *
4937
+ * All options are optional: `s.images()` accepts any image type (`"image/*"`) in
4938
+ * the default `/public/val` directory, with nullable alt text and remote disabled.
4939
+ *
4906
4940
  * @example
4907
4941
  * ```typescript
4908
4942
  * const schema = s.images({
@@ -4921,9 +4955,9 @@ function router(router, item) {
4921
4955
  * ```
4922
4956
  */
4923
4957
  var images = function images(options) {
4924
- var _options$directory, _options$alt, _options$remote;
4925
- var directory = (_options$directory = options.directory) !== null && _options$directory !== void 0 ? _options$directory : "/public/val";
4926
- var altSchema = (_options$alt = options.alt) !== null && _options$alt !== void 0 ? _options$alt : string().nullable();
4958
+ var _options$directory, _options$alt, _options$accept, _options$remote;
4959
+ var directory = (_options$directory = options === null || options === void 0 ? void 0 : options.directory) !== null && _options$directory !== void 0 ? _options$directory : "/public/val";
4960
+ var altSchema = (_options$alt = options === null || options === void 0 ? void 0 : options.alt) !== null && _options$alt !== void 0 ? _options$alt : string().nullable();
4927
4961
  var itemSchema = new ObjectSchema({
4928
4962
  width: new NumberSchema(undefined, false),
4929
4963
  height: new NumberSchema(undefined, false),
@@ -4932,9 +4966,9 @@ var images = function images(options) {
4932
4966
  }, false);
4933
4967
  return new RecordSchema(itemSchema, false, [], null, null, {
4934
4968
  type: "images",
4935
- accept: options.accept,
4969
+ accept: (_options$accept = options === null || options === void 0 ? void 0 : options.accept) !== null && _options$accept !== void 0 ? _options$accept : "image/*",
4936
4970
  directory: directory,
4937
- remote: (_options$remote = options.remote) !== null && _options$remote !== void 0 ? _options$remote : false,
4971
+ remote: (_options$remote = options === null || options === void 0 ? void 0 : options.remote) !== null && _options$remote !== void 0 ? _options$remote : false,
4938
4972
  altSchema: altSchema
4939
4973
  });
4940
4974
  };
@@ -5040,6 +5074,9 @@ var initRemote = function initRemote(config) {
5040
5074
  }
5041
5075
  return remote;
5042
5076
  };
5077
+
5078
+ // NOTE: the segments must match the ref built by createRemoteRef below and the RegEx in splitRemoteRef.
5079
+
5043
5080
  function createRemoteRef(remoteHost, _ref2) {
5044
5081
  var publicProjectId = _ref2.publicProjectId,
5045
5082
  coreVersion = _ref2.coreVersion,
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var dist_valbuildCore = require('./index-039896b7.cjs.dev.js');
5
+ var dist_valbuildCore = require('./index-24fcc47f.cjs.dev.js');
6
6
  require('./result-bb1f436e.cjs.dev.js');
7
7
 
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var dist_valbuildCore = require('./index-4da4ac5a.cjs.prod.js');
5
+ var dist_valbuildCore = require('./index-139e0a7d.cjs.prod.js');
6
6
  require('./result-787e35f6.cjs.prod.js');
7
7
 
8
8
 
@@ -1,2 +1,2 @@
1
- export { A as ArraySchema, B as BooleanSchema, e as DEFAULT_APP_HOST, D as DEFAULT_CONTENT_HOST, g as DEFAULT_VAL_REMOTE_HOST, p as DateSchema, q as DateTimeSchema, F as FATAL_ERROR_TYPES, h as FILE_REF_PROP, j as FILE_REF_SUBTYPE_TAG, o as FileSchema, G as GenericSelector, n as ImageSchema, I as Internal, K as KeyOfSchema, L as LiteralSchema, M as ModuleFilePathSep, N as NumberSchema, O as ObjectSchema, R as RecordSchema, t as RichTextSchema, r as RouteSchema, S as Schema, l as StringSchema, U as UnionSchema, V as VAL_EXTENSION, k as derefPatch, u as deserializeSchema, f as extractValModules, v as getSourcePathFromRoute, i as initVal, m as modules } from './index-8c26d8f4.esm.js';
1
+ export { A as ArraySchema, B as BooleanSchema, e as DEFAULT_APP_HOST, D as DEFAULT_CONTENT_HOST, g as DEFAULT_VAL_REMOTE_HOST, p as DateSchema, q as DateTimeSchema, F as FATAL_ERROR_TYPES, h as FILE_REF_PROP, j as FILE_REF_SUBTYPE_TAG, o as FileSchema, G as GenericSelector, n as ImageSchema, I as Internal, K as KeyOfSchema, L as LiteralSchema, M as ModuleFilePathSep, N as NumberSchema, O as ObjectSchema, R as RecordSchema, t as RichTextSchema, r as RouteSchema, S as Schema, l as StringSchema, U as UnionSchema, V as VAL_EXTENSION, k as derefPatch, u as deserializeSchema, f as extractValModules, v as getSourcePathFromRoute, i as initVal, m as modules } from './index-802570f7.esm.js';
2
2
  import './result-4bd63123.esm.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valbuild/core",
3
- "version": "0.97.1",
3
+ "version": "0.97.7",
4
4
  "private": false,
5
5
  "description": "Val - supercharged hard-coded content",
6
6
  "repository": {
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var dist_valbuildCore = require('../../dist/index-039896b7.cjs.dev.js');
5
+ var dist_valbuildCore = require('../../dist/index-24fcc47f.cjs.dev.js');
6
6
  var result = require('../../dist/result-bb1f436e.cjs.dev.js');
7
7
  var util = require('../../dist/util-b213092b.cjs.dev.js');
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var dist_valbuildCore = require('../../dist/index-4da4ac5a.cjs.prod.js');
5
+ var dist_valbuildCore = require('../../dist/index-139e0a7d.cjs.prod.js');
6
6
  var result = require('../../dist/result-787e35f6.cjs.prod.js');
7
7
  var util = require('../../dist/util-030d8a1f.cjs.prod.js');
8
8
 
@@ -1,5 +1,5 @@
1
- import { _ as _typeof, a as _slicedToArray, P as PatchError, s as splitModuleFilePathAndModulePath, b as _createClass, c as _classCallCheck, d as _toConsumableArray } from '../../dist/index-8c26d8f4.esm.js';
2
- export { P as PatchError } from '../../dist/index-8c26d8f4.esm.js';
1
+ import { _ as _typeof, a as _slicedToArray, P as PatchError, s as splitModuleFilePathAndModulePath, b as _createClass, c as _classCallCheck, d as _toConsumableArray } from '../../dist/index-802570f7.esm.js';
2
+ export { P as PatchError } from '../../dist/index-802570f7.esm.js';
3
3
  import { f as isNonEmpty, e as err, o as ok, m as map, g as flatMap, i as isErr, h as flatMapReduce, j as filterOrElse, k as mapErr, l as map$1, n as all, p as flatten, q as allT } from '../../dist/result-4bd63123.esm.js';
4
4
  import { p as pipe } from '../../dist/util-18613e99.esm.js';
5
5