@valbuild/core 0.139.0 → 0.141.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 (28) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/dist/declarations/src/extractValModules.d.ts +14 -0
  3. package/dist/declarations/src/getSha256.d.ts +55 -0
  4. package/dist/declarations/src/index.d.ts +21 -4
  5. package/dist/declarations/src/initSchema.d.ts +75 -55
  6. package/dist/declarations/src/initVal.d.ts +25 -2
  7. package/dist/declarations/src/remote/validationBasis.d.ts +3 -2
  8. package/dist/declarations/src/schema/file.d.ts +7 -0
  9. package/dist/declarations/src/schema/image.d.ts +9 -2
  10. package/dist/declarations/src/schema/index.d.ts +2 -1
  11. package/dist/declarations/src/schema/record.d.ts +16 -2
  12. package/dist/declarations/src/schema/richtext.d.ts +20 -1
  13. package/dist/declarations/src/schema/validation/ValidationFix.d.ts +1 -1
  14. package/dist/declarations/src/schema/video.d.ts +249 -0
  15. package/dist/declarations/src/schema/videoset.d.ts +141 -0
  16. package/dist/declarations/src/source/galleries.d.ts +20 -0
  17. package/dist/declarations/src/source/media.d.ts +172 -12
  18. package/dist/declarations/src/val/index.d.ts +4 -2
  19. package/dist/{index-8920ba3f.cjs.prod.js → index-13cf45b1.cjs.prod.js} +1547 -134
  20. package/dist/{index-d949d13b.esm.js → index-a6c53286.esm.js} +1543 -135
  21. package/dist/{index-e13b725c.cjs.dev.js → index-fae161b5.cjs.dev.js} +1547 -134
  22. package/dist/valbuild-core.cjs.dev.js +6 -1
  23. package/dist/valbuild-core.cjs.prod.js +6 -1
  24. package/dist/valbuild-core.esm.js +1 -1
  25. package/package.json +1 -1
  26. package/patch/dist/valbuild-core-patch.cjs.dev.js +1 -1
  27. package/patch/dist/valbuild-core-patch.cjs.prod.js +1 -1
  28. package/patch/dist/valbuild-core-patch.esm.js +2 -2
@@ -2752,6 +2752,28 @@ var discriminatedUnion = function discriminatedUnion(key) {
2752
2752
  return new DiscriminatedUnionSchema(key, objects);
2753
2753
  };
2754
2754
 
2755
+ function _objectWithoutPropertiesLoose(r, e) {
2756
+ if (null == r) return {};
2757
+ var t = {};
2758
+ for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
2759
+ if (-1 !== e.indexOf(n)) continue;
2760
+ t[n] = r[n];
2761
+ }
2762
+ return t;
2763
+ }
2764
+
2765
+ function _objectWithoutProperties(e, t) {
2766
+ if (null == e) return {};
2767
+ var o,
2768
+ r,
2769
+ i = _objectWithoutPropertiesLoose(e, t);
2770
+ if (Object.getOwnPropertySymbols) {
2771
+ var n = Object.getOwnPropertySymbols(e);
2772
+ for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]);
2773
+ }
2774
+ return i;
2775
+ }
2776
+
2755
2777
  var RegEx$1 = /^(https?:\/\/[^/]+)\/file\/p\/([^/]+)\/b\/([^/]+)\/v\/([^/]+)\/h\/([^/]+)\/f\/([^/]+)\/p\/(.+)$/;
2756
2778
  function splitRemoteRef(ref) {
2757
2779
  if (ref[0] === "/") {
@@ -2793,6 +2815,8 @@ function splitRemoteRef(ref) {
2793
2815
  };
2794
2816
  }
2795
2817
 
2818
+ var _excluded = ["poster", "captions"];
2819
+
2796
2820
  /** What must stay in frame when a page crops an image. */
2797
2821
 
2798
2822
  /**
@@ -2801,6 +2825,76 @@ function splitRemoteRef(ref) {
2801
2825
  * here. One place per fact.
2802
2826
  */
2803
2827
 
2828
+ /**
2829
+ * The still shown before a video plays — `<video poster>`.
2830
+ *
2831
+ * Derived, like a video's `width` and `height`: the Studio grabs the frame at
2832
+ * {@link VideoSource.posterTime} and uploads it as an image, so a page can show
2833
+ * it without decoding the video. It is a media object of its own (a `path`,
2834
+ * and a `patch_id` while unpublished) because it is a file of its own.
2835
+ */
2836
+
2837
+ /**
2838
+ * One text track of a video — `<track src srclang label kind default>`.
2839
+ *
2840
+ * `path` is a WebVTT file, uploaded like any other file.
2841
+ */
2842
+
2843
+ /**
2844
+ * What a video FIELD authors, on top of the file it names: the description,
2845
+ * the crop, the poster, the trim and the caption tracks.
2846
+ *
2847
+ * This is the whole value of a field picked from an `s.videoset()`
2848
+ * (`s.video(videosetVal)`) — the mime type, size and length live in the set,
2849
+ * keyed by `path`, and repeating them here is how two copies of one fact get
2850
+ * to disagree. The poster, captions and times stay with the FIELD: one video
2851
+ * used in two places can be trimmed and captioned differently in each.
2852
+ *
2853
+ * Times are in seconds from the start of the file.
2854
+ */
2855
+
2856
+ /**
2857
+ * What one page chooses about a video — as opposed to what is true of the
2858
+ * file. A field (`s.video()`) carries these for itself; the entry of a set
2859
+ * (`s.videoset()`) carries them as DEFAULTS, which every field picked from the
2860
+ * set starts from and may override key by key. See {@link fillFromGallery}.
2861
+ */
2862
+
2863
+ /**
2864
+ * A video: a progressive file (`video/mp4`, `video/webm`) or an HLS stream
2865
+ * (`application/vnd.apple.mpegurl`, whose `path` is the master playlist).
2866
+ *
2867
+ * `mimeType` is REQUIRED on a video of its own: a page has to know which of
2868
+ * the two it is holding before it can play it at all — an `.m3u8` handed to a
2869
+ * `<video src>` plays only in Safari. (What tells a video's TYPE apart from an
2870
+ * image's is its declared keys, see {@link IsVideoSource}, because a field
2871
+ * picked from a set has no `mimeType` of its own.)
2872
+ *
2873
+ * `width`, `height`, `duration` and `mimeType` are read from the bytes;
2874
+ * everything else is authored.
2875
+ */
2876
+
2877
+ /**
2878
+ * Whether a source TYPE is a video's.
2879
+ *
2880
+ * Every media source is "a `path` plus optional fields", so assignability
2881
+ * cannot tell an image from a video: an image is assignable to a set-backed
2882
+ * video and the other way round. The keys a type DECLARES can — only a video
2883
+ * declares `posterTime` — so the readers' conditional types ask this before
2884
+ * they ask about images.
2885
+ */
2886
+
2887
+ /** The mime type of an HLS master playlist. */
2888
+ var HLS_MIME_TYPE = "application/vnd.apple.mpegurl";
2889
+
2890
+ /** Whether a video is an HLS stream rather than a single progressive file. */
2891
+ function isHlsVideo(src) {
2892
+ if (src.mimeType !== undefined) {
2893
+ return src.mimeType === HLS_MIME_TYPE || src.mimeType.toLowerCase() === "application/x-mpegurl";
2894
+ }
2895
+ return src.path.split("?")[0].toLowerCase().endsWith(".m3u8");
2896
+ }
2897
+
2804
2898
  /**
2805
2899
  * The structural supertype of every media source.
2806
2900
  *
@@ -2854,66 +2948,181 @@ function resolveMedia(src) {
2854
2948
  url: mediaUrl(src)
2855
2949
  });
2856
2950
  }
2951
+
2952
+ /**
2953
+ * A video as a reader sees it: every file it names — the video itself, its
2954
+ * poster and each caption track — carries the URL its bytes are served from.
2955
+ *
2956
+ * Each gets its own {@link mediaUrl} because each is its own file with its own
2957
+ * `patch_id`: replacing only the poster drafts the poster, and the video keeps
2958
+ * its published URL.
2959
+ */
2960
+
2961
+ /**
2962
+ * The single implementation of {@link ResolvedVideo}. `url` is passed in as a
2963
+ * function so the reader can tag the video's own URL (stega) without tagging
2964
+ * the poster's and the captions', which are not where an edit lands.
2965
+ */
2966
+ function resolveVideo(src) {
2967
+ var videoUrl = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : mediaUrl;
2968
+ var poster = src.poster,
2969
+ captions = src.captions,
2970
+ rest = _objectWithoutProperties(src, _excluded);
2971
+ var resolved = _objectSpread2(_objectSpread2({}, rest), {}, {
2972
+ url: videoUrl(src)
2973
+ });
2974
+ if (poster && _typeof(poster) === "object" && typeof poster.path === "string") {
2975
+ return withCaptions(_objectSpread2(_objectSpread2({}, resolved), {}, {
2976
+ poster: _objectSpread2(_objectSpread2({}, poster), {}, {
2977
+ url: mediaUrl(poster)
2978
+ })
2979
+ }), captions);
2980
+ }
2981
+ return withCaptions(resolved, captions);
2982
+ }
2983
+ function withCaptions(resolved, captions) {
2984
+ if (!Array.isArray(captions)) {
2985
+ return resolved;
2986
+ }
2987
+ return _objectSpread2(_objectSpread2({}, resolved), {}, {
2988
+ captions: captions.filter(function (track) {
2989
+ return !!track && _typeof(track) === "object" && typeof track.path === "string";
2990
+ }).map(function (track) {
2991
+ return _objectSpread2(_objectSpread2({}, track), {}, {
2992
+ url: mediaUrl(track)
2993
+ });
2994
+ })
2995
+ });
2996
+ }
2857
2997
  function isMediaSchema(schema) {
2858
- return _typeof(schema) === "object" && schema !== null && "type" in schema && (schema.type === "image" || schema.type === "file");
2998
+ return _typeof(schema) === "object" && schema !== null && "type" in schema && (schema.type === "image" || schema.type === "file" || schema.type === "video");
2859
2999
  }
2860
3000
 
2861
3001
  /**
2862
- * Fill in what a gallery-backed field does not carry itself.
2863
- *
2864
- * `s.image(galleryModule)` stores only `{path, alt?, hotspot?}` — the
2865
- * dimensions and mime type live in the gallery module, keyed by path. This is
2866
- * the single implementation of that lookup; core, stega/RSC and the Studio all
2867
- * call it rather than each rolling their own.
3002
+ * What a gallery entry holds as a DEFAULT for the fields picked from it, in
3003
+ * the groups a field overrides together.
2868
3004
  *
2869
- * `alt` is filled in too, because a gallery holds the alt text an editor typed
2870
- * once for a file used in several places — but only when the field does not
2871
- * have its own, so a per-image override wins. A gallery whose `alt` schema is a
2872
- * locale record holds an object rather than a string; that is left alone rather
2873
- * than copied into a field typed `string`, and making the override
2874
- * locale-shaped is a separate change.
3005
+ * Per key, not per value: a field that has set its own `startTime` still
3006
+ * takes the gallery's `endTime`. The poster is the one pair — `poster` is the
3007
+ * frame at `posterTime`, so a field with either has chosen its own still, and
3008
+ * taking the other half from the gallery would describe a frame nobody chose.
3009
+ */
3010
+ var GALLERY_DEFAULT_GROUPS = [["alt"], ["hotspot"], ["poster", "posterTime"], ["startTime"], ["endTime"], ["captions"]];
3011
+
3012
+ /** What is true of the file, and the gallery's alone. */
3013
+ var GALLERY_FILE_FACTS = ["width", "height", "mimeType", "duration"];
3014
+
3015
+ /**
3016
+ * The gallery entry a gallery-backed value names, or `null` when it is not
3017
+ * gallery-backed or the gallery has no such entry.
2875
3018
  *
2876
3019
  * The double lookup is load-bearing: a remote gallery keys its entries by the
2877
3020
  * remote URL while the file itself stays on disk under its local path.
2878
3021
  */
2879
- function fillFromGallery(src, schema, getModuleSource) {
3022
+ function galleryEntryOf(src, schema, getModuleSource) {
2880
3023
  if (!isMediaSchema(schema) || !schema.referencedModule) {
2881
- return src;
3024
+ return null;
2882
3025
  }
2883
3026
  var moduleSource = getModuleSource(schema.referencedModule);
2884
- if (!moduleSource || _typeof(moduleSource) !== "object" || Array.isArray(moduleSource)) {
2885
- return src;
3027
+ if (!isPlainObject(moduleSource)) {
3028
+ return null;
2886
3029
  }
2887
- var entries = moduleSource;
2888
- var key = src.path in entries ? src.path : null;
3030
+ var key = src.path in moduleSource ? src.path : null;
2889
3031
  if (key === null) {
2890
3032
  var splitRemoteRefRes = splitRemoteRef(src.path);
2891
- if (splitRemoteRefRes.status === "success" && splitRemoteRefRes.filePath in entries) {
3033
+ if (splitRemoteRefRes.status === "success" && splitRemoteRefRes.filePath in moduleSource) {
2892
3034
  key = splitRemoteRefRes.filePath;
2893
3035
  }
2894
3036
  }
2895
3037
  if (key === null) {
2896
- return src;
3038
+ return null;
2897
3039
  }
2898
- var entry = entries[key];
2899
- if (!entry || _typeof(entry) !== "object" || Array.isArray(entry)) {
3040
+ var entry = moduleSource[key];
3041
+ return isPlainObject(entry) ? entry : null;
3042
+ }
3043
+
3044
+ /**
3045
+ * The value a gallery-backed field means: its own keys, and the gallery's
3046
+ * where it has none.
3047
+ *
3048
+ * `s.image(galleryModule)` and `s.video(videosModule)` store only what one
3049
+ * page chose — the rest lives in the gallery, keyed by path. Two things come
3050
+ * from there:
3051
+ *
3052
+ * - **What is true of the file** — dimensions, mime type, length. The field
3053
+ * never has these, so they are always the gallery's.
3054
+ * - **The defaults** — description, focal point, a video's poster, start, end
3055
+ * and captions ({@link GALLERY_DEFAULT_GROUPS}). An editor sets them once
3056
+ * on the gallery entry; a field that sets its own wins, KEY BY KEY.
3057
+ *
3058
+ * The single implementation of that merge: core, stega/RSC and the Studio all
3059
+ * call it rather than each rolling their own, so a page and the Studio's
3060
+ * preview of it cannot disagree about which poster a video has.
3061
+ *
3062
+ * A gallery whose `alt` schema is a locale record holds an object rather than
3063
+ * a string; that is left alone rather than copied into a field typed
3064
+ * `string`, and making the override locale-shaped is a separate change.
3065
+ */
3066
+ function fillFromGallery(src, schema, getModuleSource) {
3067
+ var entry = galleryEntryOf(src, schema, getModuleSource);
3068
+ if (entry === null) {
2900
3069
  return src;
2901
3070
  }
2902
- var _ref = entry,
2903
- width = _ref.width,
2904
- height = _ref.height,
2905
- mimeType = _ref.mimeType,
2906
- alt = _ref.alt;
2907
- var hasOwnAlt = typeof src.alt === "string";
2908
- return _objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, src), width !== undefined ? {
2909
- width: width
2910
- } : {}), height !== undefined ? {
2911
- height: height
2912
- } : {}), mimeType !== undefined ? {
2913
- mimeType: mimeType
2914
- } : {}), !hasOwnAlt && typeof alt === "string" ? {
2915
- alt: alt
2916
- } : {});
3071
+ var own = _objectSpread2({}, src);
3072
+ var filled = {};
3073
+ var _iterator = _createForOfIteratorHelper(GALLERY_FILE_FACTS),
3074
+ _step;
3075
+ try {
3076
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
3077
+ var key = _step.value;
3078
+ if (entry[key] !== undefined) {
3079
+ filled[key] = entry[key];
3080
+ }
3081
+ }
3082
+ } catch (err) {
3083
+ _iterator.e(err);
3084
+ } finally {
3085
+ _iterator.f();
3086
+ }
3087
+ var _iterator2 = _createForOfIteratorHelper(GALLERY_DEFAULT_GROUPS),
3088
+ _step2;
3089
+ try {
3090
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
3091
+ var group = _step2.value;
3092
+ if (group.some(function (key) {
3093
+ return own[key] !== undefined;
3094
+ })) {
3095
+ continue;
3096
+ }
3097
+ var _iterator3 = _createForOfIteratorHelper(group),
3098
+ _step3;
3099
+ try {
3100
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
3101
+ var _key = _step3.value;
3102
+ var value = entry[_key];
3103
+ if (value === undefined || value === null) {
3104
+ continue;
3105
+ }
3106
+ if (_key === "alt" && typeof value !== "string") {
3107
+ continue;
3108
+ }
3109
+ filled[_key] = value;
3110
+ }
3111
+ } catch (err) {
3112
+ _iterator3.e(err);
3113
+ } finally {
3114
+ _iterator3.f();
3115
+ }
3116
+ }
3117
+ } catch (err) {
3118
+ _iterator2.e(err);
3119
+ } finally {
3120
+ _iterator2.f();
3121
+ }
3122
+ return _objectSpread2(_objectSpread2({}, src), filled);
3123
+ }
3124
+ function isPlainObject(value) {
3125
+ return _typeof(value) === "object" && value !== null && !Array.isArray(value);
2917
3126
  }
2918
3127
 
2919
3128
  var EXT_TO_MIME_TYPES = {
@@ -3444,6 +3653,8 @@ var EXT_TO_MIME_TYPES = {
3444
3653
  qam: "application/vnd.epson.quickanime",
3445
3654
  qfx: "application/vnd.intu.qfx",
3446
3655
  mov: "video/quicktime",
3656
+ vtt: "text/vtt",
3657
+ mkv: "video/x-matroska",
3447
3658
  rar: "application/x-rar-compressed",
3448
3659
  ram: "audio/x-pn-realaudio",
3449
3660
  rmp: "audio/x-pn-realaudio-plugin",
@@ -4095,6 +4306,19 @@ var ImageSchema = /*#__PURE__*/function (_Schema) {
4095
4306
  /** An image of its own, carrying its own dimensions and mime type. */
4096
4307
 
4097
4308
  function image(options, galleryOptions) {
4309
+ return imageOf(false, options, galleryOptions);
4310
+ }
4311
+
4312
+ /**
4313
+ * `s.image()` for a project whose media is all remote (`files.remote` in
4314
+ * `val.config.ts`): the same schema, with `.remote()` already applied. See
4315
+ * `initSchema`.
4316
+ */
4317
+
4318
+ function remoteImage(options, galleryOptions) {
4319
+ return imageOf(true, options, galleryOptions);
4320
+ }
4321
+ function imageOf(remote, options, galleryOptions) {
4098
4322
  var isModule = !!options && !!Internal.getValPath(options);
4099
4323
  if (isModule) {
4100
4324
  var allModules = {};
@@ -4108,9 +4332,9 @@ function image(options, galleryOptions) {
4108
4332
  }
4109
4333
  return new ImageSchema((galleryOptions === null || galleryOptions === void 0 ? void 0 : galleryOptions.encode) !== undefined ? {
4110
4334
  encode: galleryOptions.encode
4111
- } : {}, false, false, [], allModules);
4335
+ } : {}, false, remote, [], allModules);
4112
4336
  }
4113
- return new ImageSchema(options);
4337
+ return new ImageSchema(options, false, remote);
4114
4338
  }
4115
4339
 
4116
4340
  var RouteSchema = /*#__PURE__*/function (_Schema) {
@@ -4369,6 +4593,7 @@ var RichTextSchema = /*#__PURE__*/function (_Schema) {
4369
4593
  var isHidden = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
4370
4594
  var description = arguments.length > 5 ? arguments[5] : undefined;
4371
4595
  var previewInput = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : null;
4596
+ var imgRemote = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : false;
4372
4597
  _classCallCheck(this, RichTextSchema);
4373
4598
  _this = _callSuper(this, RichTextSchema);
4374
4599
  _this.options = options;
@@ -4378,6 +4603,7 @@ var RichTextSchema = /*#__PURE__*/function (_Schema) {
4378
4603
  _this.isHidden = isHidden;
4379
4604
  _this.description = description;
4380
4605
  _this.previewInput = previewInput;
4606
+ _this.imgRemote = imgRemote;
4381
4607
  return _this;
4382
4608
  }
4383
4609
 
@@ -4404,7 +4630,7 @@ var RichTextSchema = /*#__PURE__*/function (_Schema) {
4404
4630
  return _createClass(RichTextSchema, [{
4405
4631
  key: "describe",
4406
4632
  value: function describe(description) {
4407
- return new RichTextSchema(this.options, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.previewInput);
4633
+ return new RichTextSchema(this.options, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.previewInput, this.imgRemote);
4408
4634
  }
4409
4635
 
4410
4636
  /**
@@ -4424,7 +4650,7 @@ var RichTextSchema = /*#__PURE__*/function (_Schema) {
4424
4650
  value: function maxLength(max) {
4425
4651
  return new RichTextSchema(_objectSpread2(_objectSpread2({}, this.options), {}, {
4426
4652
  maxLength: max
4427
- }), this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.previewInput);
4653
+ }), this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.previewInput, this.imgRemote);
4428
4654
  }
4429
4655
 
4430
4656
  /**
@@ -4444,7 +4670,7 @@ var RichTextSchema = /*#__PURE__*/function (_Schema) {
4444
4670
  value: function minLength(min) {
4445
4671
  return new RichTextSchema(_objectSpread2(_objectSpread2({}, this.options), {}, {
4446
4672
  minLength: min
4447
- }), this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.previewInput);
4673
+ }), this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.previewInput, this.imgRemote);
4448
4674
  }
4449
4675
 
4450
4676
  /**
@@ -4472,7 +4698,7 @@ var RichTextSchema = /*#__PURE__*/function (_Schema) {
4472
4698
  }, {
4473
4699
  key: "validate",
4474
4700
  value: function validate(validationFunction) {
4475
- return new RichTextSchema(this.options, this.opt, [].concat(_toConsumableArray(this.customValidateFunctions), [validationFunction]), this.isReadonly, this.isHidden, this.description, this.previewInput);
4701
+ return new RichTextSchema(this.options, this.opt, [].concat(_toConsumableArray(this.customValidateFunctions), [validationFunction]), this.isReadonly, this.isHidden, this.description, this.previewInput, this.imgRemote);
4476
4702
  }
4477
4703
  }, {
4478
4704
  key: "executeValidate",
@@ -4647,7 +4873,7 @@ var RichTextSchema = /*#__PURE__*/function (_Schema) {
4647
4873
  }
4648
4874
  var srcPath = unsafeCreateSourcePath(path, "src");
4649
4875
  var imgSchema = _this2.options.img;
4650
- var imageValidationErrors = _typeof(imgSchema) === "object" ? imgSchema["executeValidate"](srcPath, node.src) : new ImageSchema({}, false, false)["executeValidate"](srcPath, node.src);
4876
+ var imageValidationErrors = _typeof(imgSchema) === "object" ? imgSchema["executeValidate"](srcPath, node.src) : new ImageSchema({}, false, _this2.imgRemote)["executeValidate"](srcPath, node.src);
4651
4877
  if (imageValidationErrors) {
4652
4878
  for (var _validationErrorPathS in imageValidationErrors) {
4653
4879
  var _current$_validationE;
@@ -4916,19 +5142,19 @@ var RichTextSchema = /*#__PURE__*/function (_Schema) {
4916
5142
  }, {
4917
5143
  key: "nullable",
4918
5144
  value: function nullable() {
4919
- return new RichTextSchema(this.options, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.previewInput);
5145
+ return new RichTextSchema(this.options, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.previewInput, this.imgRemote);
4920
5146
  }
4921
5147
  }, {
4922
5148
  key: "readonly",
4923
5149
  value: function readonly() {
4924
5150
  var isReadonly = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
4925
- return new RichTextSchema(this.options, this.opt, this.customValidateFunctions, isReadonly, this.isHidden, this.description, this.previewInput);
5151
+ return new RichTextSchema(this.options, this.opt, this.customValidateFunctions, isReadonly, this.isHidden, this.description, this.previewInput, this.imgRemote);
4926
5152
  }
4927
5153
  }, {
4928
5154
  key: "hidden",
4929
5155
  value: function hidden() {
4930
5156
  var isHidden = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
4931
- return new RichTextSchema(this.options, this.opt, this.customValidateFunctions, this.isReadonly, isHidden, this.description, this.previewInput);
5157
+ return new RichTextSchema(this.options, this.opt, this.customValidateFunctions, this.isReadonly, isHidden, this.description, this.previewInput, this.imgRemote);
4932
5158
  }
4933
5159
  }, {
4934
5160
  key: "executeCustomValidateAt",
@@ -4956,7 +5182,7 @@ var RichTextSchema = /*#__PURE__*/function (_Schema) {
4956
5182
  }, {
4957
5183
  key: "preview",
4958
5184
  value: function preview(select) {
4959
- return new RichTextSchema(this.options, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, select);
5185
+ return new RichTextSchema(this.options, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, select, this.imgRemote);
4960
5186
  }
4961
5187
  }, {
4962
5188
  key: "executePreviewItem",
@@ -5002,7 +5228,10 @@ var RichTextSchema = /*#__PURE__*/function (_Schema) {
5002
5228
  ul: this.options.ul,
5003
5229
  ol: this.options.ol,
5004
5230
  a: serializeAnchorSchema(),
5005
- img: this.options.img && _typeof(this.options.img) === "object" ? this.options.img["executeSerialize"]() : this.options.img
5231
+ img: this.options.img && _typeof(this.options.img) === "object" ? this.options.img["executeSerialize"]() :
5232
+ // The Studio decides local or remote by the serialized image
5233
+ // schema, and `true` carries no such answer.
5234
+ this.options.img && this.imgRemote ? new ImageSchema(undefined, false, true)["executeSerialize"]() : this.options.img
5006
5235
  };
5007
5236
  return {
5008
5237
  type: "richtext",
@@ -5021,6 +5250,14 @@ var richtext = function richtext(options) {
5021
5250
  return new RichTextSchema(options !== null && options !== void 0 ? options : {});
5022
5251
  };
5023
5252
 
5253
+ /**
5254
+ * `s.richtext()` for a project whose media is all remote (`files.remote` in
5255
+ * `val.config.ts`): `img: true` is a remote image. See `initSchema`.
5256
+ */
5257
+ var remoteRichtext = function remoteRichtext(options) {
5258
+ return new RichTextSchema(options !== null && options !== void 0 ? options : {}, false, [], false, false, undefined, null, true);
5259
+ };
5260
+
5024
5261
  function _regeneratorDefine(e, r, n, t) {
5025
5262
  var i = Object.defineProperty;
5026
5263
  try {
@@ -5469,6 +5706,207 @@ function missingDeclaredKeys(declared, present) {
5469
5706
  });
5470
5707
  }
5471
5708
 
5709
+ /**
5710
+ * The checks of what one page chose about a video — its times, focal point,
5711
+ * poster and captions — shared by the two places those choices are made: a
5712
+ * field (`s.video()`) and the entry of a set (`s.videoset()`), which holds the
5713
+ * same choices as the defaults every field picked from it starts from.
5714
+ */
5715
+
5716
+ function isRecord(value) {
5717
+ return _typeof(value) === "object" && value !== null && !Array.isArray(value);
5718
+ }
5719
+ function stripQuery$1(path) {
5720
+ return path.split("?")[0];
5721
+ }
5722
+
5723
+ /**
5724
+ * What is checked, typed as what it IS rather than what it should be: a
5725
+ * field's value and a set's entry both reach here from hand-written JSON
5726
+ * that never saw the type.
5727
+ */
5728
+
5729
+ function isNonNegativeNumber(value) {
5730
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
5731
+ }
5732
+ function validateTimes(src, duration) {
5733
+ var errors = [];
5734
+ var _iterator = _createForOfIteratorHelper(["posterTime", "startTime", "endTime"]),
5735
+ _step;
5736
+ try {
5737
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
5738
+ var key = _step.value;
5739
+ var value = src[key];
5740
+ if (value === undefined) {
5741
+ continue;
5742
+ }
5743
+ if (!isNonNegativeNumber(value)) {
5744
+ errors.push({
5745
+ message: "'".concat(key, "' must be a number of seconds, 0 or more. Got: ").concat(JSON.stringify(value)),
5746
+ value: src
5747
+ });
5748
+ } else if (duration !== undefined && value > duration) {
5749
+ errors.push({
5750
+ message: "'".concat(key, "' (").concat(value, "s) is after the end of the video (").concat(duration, "s)."),
5751
+ value: src
5752
+ });
5753
+ }
5754
+ }
5755
+ } catch (err) {
5756
+ _iterator.e(err);
5757
+ } finally {
5758
+ _iterator.f();
5759
+ }
5760
+ var startTime = src.startTime,
5761
+ endTime = src.endTime;
5762
+ if (isNonNegativeNumber(startTime) && isNonNegativeNumber(endTime) && startTime >= endTime) {
5763
+ errors.push({
5764
+ message: "'startTime' (".concat(startTime, "s) must be before 'endTime' (").concat(endTime, "s)."),
5765
+ value: src
5766
+ });
5767
+ }
5768
+ return errors;
5769
+ }
5770
+ function validateHotspot(src) {
5771
+ if (src.hotspot === undefined) {
5772
+ return [];
5773
+ }
5774
+ var hotspot = src.hotspot;
5775
+ if (!isRecord(hotspot) || typeof hotspot.x !== "number" || typeof hotspot.y !== "number" || hotspot.x < 0 || hotspot.x > 1 || hotspot.y < 0 || hotspot.y > 1) {
5776
+ return [{
5777
+ message: "'hotspot' must be { x, y } with both between 0 and 1.",
5778
+ value: src
5779
+ }];
5780
+ }
5781
+ return [];
5782
+ }
5783
+ function validatePoster(src) {
5784
+ if (src.poster === undefined) {
5785
+ return [];
5786
+ }
5787
+ var poster = src.poster;
5788
+ if (!isRecord(poster) || typeof poster.path !== "string") {
5789
+ return [{
5790
+ message: "'poster' must be an image object with a 'path'.",
5791
+ value: src
5792
+ }];
5793
+ }
5794
+ var mimeType = filenameToMimeType(stripQuery$1(poster.path));
5795
+ if (!mimeType || !mimeType.startsWith("image/")) {
5796
+ return [{
5797
+ message: "The poster must be an image. Got: ".concat(poster.path),
5798
+ value: src
5799
+ }];
5800
+ }
5801
+ return [];
5802
+ }
5803
+ var CAPTION_KINDS = ["subtitles", "captions"];
5804
+ function validateCaptions(src) {
5805
+ if (src.captions === undefined) {
5806
+ return [];
5807
+ }
5808
+ if (!Array.isArray(src.captions)) {
5809
+ return [{
5810
+ message: "'captions' must be an array.",
5811
+ value: src
5812
+ }];
5813
+ }
5814
+ var errors = [];
5815
+ var defaults = 0;
5816
+ var seen = new Set();
5817
+ src.captions.forEach(function (track, i) {
5818
+ var at = "Caption track ".concat(i + 1);
5819
+ if (!isRecord(track)) {
5820
+ errors.push({
5821
+ message: "".concat(at, " must be an object."),
5822
+ value: src
5823
+ });
5824
+ return;
5825
+ }
5826
+ var t = track;
5827
+ if (typeof t.path !== "string") {
5828
+ errors.push({
5829
+ message: "".concat(at, " has no 'path'."),
5830
+ value: src
5831
+ });
5832
+ } else if (filenameToMimeType(stripQuery$1(t.path)) !== "text/vtt") {
5833
+ errors.push({
5834
+ message: "".concat(at, " must be a WebVTT (.vtt) file. Got: ").concat(t.path),
5835
+ value: src
5836
+ });
5837
+ }
5838
+ if (typeof t.srclang !== "string" || t.srclang.trim() === "") {
5839
+ errors.push({
5840
+ message: "".concat(at, " needs a language ('srclang'), e.g. \"en\"."),
5841
+ value: src
5842
+ });
5843
+ }
5844
+ if (t.label !== undefined && typeof t.label !== "string") {
5845
+ errors.push({
5846
+ message: "".concat(at, ": 'label' must be a string."),
5847
+ value: src
5848
+ });
5849
+ }
5850
+ if (t.kind !== undefined && (typeof t.kind !== "string" || !CAPTION_KINDS.includes(t.kind))) {
5851
+ errors.push({
5852
+ message: "".concat(at, ": 'kind' must be \"subtitles\" or \"captions\"."),
5853
+ value: src
5854
+ });
5855
+ }
5856
+ if (t["default"] !== undefined && typeof t["default"] !== "boolean") {
5857
+ errors.push({
5858
+ message: "".concat(at, ": 'default' must be true or false."),
5859
+ value: src
5860
+ });
5861
+ }
5862
+ if (t["default"] === true) {
5863
+ defaults++;
5864
+ }
5865
+ if (typeof t.srclang === "string") {
5866
+ var _t$kind;
5867
+ var key = "".concat((_t$kind = t.kind) !== null && _t$kind !== void 0 ? _t$kind : "subtitles", ":").concat(t.srclang);
5868
+ if (seen.has(key)) {
5869
+ errors.push({
5870
+ message: "".concat(at, " repeats the language '").concat(t.srclang, "'."),
5871
+ value: src
5872
+ });
5873
+ }
5874
+ seen.add(key);
5875
+ }
5876
+ });
5877
+ if (defaults > 1) {
5878
+ errors.push({
5879
+ message: "At most one caption track can be the default. ".concat(defaults, " are."),
5880
+ value: src
5881
+ });
5882
+ }
5883
+ return errors;
5884
+ }
5885
+
5886
+ function isObject(value) {
5887
+ return _typeof(value) === "object" && value !== null && !Array.isArray(value);
5888
+ }
5889
+
5890
+ /** What a media collection (`s.imageset`, `s.fileset`, `s.videoset`) holds. */
5891
+
5892
+ /**
5893
+ * The fix code a media collection of `type` reports for `check`.
5894
+ *
5895
+ * One function rather than a ternary at each site: there were two kinds of
5896
+ * collection when every site was written as `type === "images" ? … : …`, and
5897
+ * a third kind silently took the files branch.
5898
+ */
5899
+ function mediaCollectionFix(type, check) {
5900
+ return "".concat(type, ":").concat(check);
5901
+ }
5902
+
5903
+ /** The singular a message uses for one entry of a collection of `type`. */
5904
+ var MEDIA_ENTRY_LABEL = {
5905
+ files: "file",
5906
+ images: "image",
5907
+ videos: "video"
5908
+ };
5909
+
5472
5910
  /**
5473
5911
  * `.render(...)` is for records whose entries the generic record editor draws.
5474
5912
  * A router's entries are pages and a gallery's are media, and both have UIs of
@@ -5660,7 +6098,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5660
6098
  _iterator.f();
5661
6099
  }
5662
6100
  if (this.mediaOptions) {
5663
- var checkFix = this.mediaOptions.type === "images" ? "images:check-unique-folder" : "files:check-unique-folder";
6101
+ var checkFix = mediaCollectionFix(this.mediaOptions.type, "check-unique-folder");
5664
6102
  var uniqueCheckError = {
5665
6103
  message: "Gallery directory '".concat(this.mediaOptions.dir, "' must be unique across all galleries"),
5666
6104
  value: {
@@ -5678,7 +6116,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5678
6116
  } else {
5679
6117
  error = _defineProperty({}, path, [uniqueCheckError]);
5680
6118
  }
5681
- var allFilesCheckFix = this.mediaOptions.type === "images" ? "images:check-all-files" : "files:check-all-files";
6119
+ var allFilesCheckFix = mediaCollectionFix(this.mediaOptions.type, "check-all-files");
5682
6120
  var allFilesCheckError = {
5683
6121
  message: "Directory '".concat(this.mediaOptions.dir, "' may have files not tracked by this gallery"),
5684
6122
  value: {
@@ -5732,7 +6170,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5732
6170
  _this2.markKeyErrorsAtPath(keyErr, subPath);
5733
6171
  }
5734
6172
  error = _this2.mergeValidationErrors(error, keyErr);
5735
- var entryErr = _this2.validateMediaEntry(subPath, elem);
6173
+ var entryErr = _this2.validateMediaEntry(subPath, key, elem);
5736
6174
  if (entryErr) {
5737
6175
  _this2.markKeyErrorsAtPath(entryErr, subPath);
5738
6176
  }
@@ -5846,8 +6284,8 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5846
6284
  dir = _this$mediaOptions.dir,
5847
6285
  isRemote = _this$mediaOptions.remote,
5848
6286
  type = _this$mediaOptions.type;
5849
- var mediaLabel = type === "images" ? "images" : "files";
5850
- var checkRemoteFix = type === "images" ? "images:check-remote" : "files:check-remote";
6287
+ var mediaLabel = type;
6288
+ var checkRemoteFix = mediaCollectionFix(type, "check-remote");
5851
6289
  var isRemoteUrl = this.isRemoteUrl(key);
5852
6290
  var isLocalPath = key === dir || key.startsWith(dir + "/");
5853
6291
  if (isRemote) {
@@ -5880,9 +6318,9 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5880
6318
  }]);
5881
6319
  }
5882
6320
  // Local path in a remote gallery: needs to be uploaded to remote.
5883
- var uploadRemoteFix = type === "images" ? "images:upload-remote" : "files:upload-remote";
6321
+ var uploadRemoteFix = mediaCollectionFix(type, "upload-remote");
5884
6322
  return _defineProperty({}, path, [{
5885
- message: "Expected a remote ".concat(type === "images" ? "image" : "file", ", but got a local path. Use Val tooling (CLI --fix, VS Code extension, or Val Studio) to upload it. Got: ").concat(key),
6323
+ message: "Expected a remote ".concat(MEDIA_ENTRY_LABEL[type], ", but got a local path. Use Val tooling (CLI --fix, VS Code extension, or Val Studio) to upload it. Got: ").concat(key),
5886
6324
  value: key,
5887
6325
  fixes: [uploadRemoteFix]
5888
6326
  }]);
@@ -5906,7 +6344,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5906
6344
  }
5907
6345
  }, {
5908
6346
  key: "validateMediaEntry",
5909
- value: function validateMediaEntry(path, entry) {
6347
+ value: function validateMediaEntry(path, key, entry) {
5910
6348
  if (!this.mediaOptions) {
5911
6349
  return false;
5912
6350
  }
@@ -5922,6 +6360,40 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5922
6360
  }
5923
6361
  var entryObj = entry;
5924
6362
  var errors = [];
6363
+ if (type === "videos") {
6364
+ // What is read from the bytes. Missing is a fix (the CLI reads them,
6365
+ // from disk or — for a remote entry — over HTTP with Range requests);
6366
+ // present and wrong is an error, because a fix that overwrote an
6367
+ // authored number would be guessing which of the two was right.
6368
+ var missing = ["mimeType", "width", "height", "duration"].filter(function (k) {
6369
+ return entryObj[k] === undefined;
6370
+ });
6371
+ if (missing.length > 0) {
6372
+ return _defineProperty({}, path, [{
6373
+ message: "Video metadata is missing: ".concat(missing.join(", "), "."),
6374
+ value: entry,
6375
+ fixes: ["videos:add-metadata"]
6376
+ }]);
6377
+ }
6378
+ var _iterator3 = _createForOfIteratorHelper(["width", "height", "duration"]),
6379
+ _step3;
6380
+ try {
6381
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
6382
+ var k = _step3.value;
6383
+ var value = entryObj[k];
6384
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
6385
+ errors.push({
6386
+ message: "Expected '".concat(k, "' to be a positive number, got '").concat(JSON.stringify(value), "'"),
6387
+ value: entry
6388
+ });
6389
+ }
6390
+ }
6391
+ } catch (err) {
6392
+ _iterator3.e(err);
6393
+ } finally {
6394
+ _iterator3.f();
6395
+ }
6396
+ }
5925
6397
  if (type === "images") {
5926
6398
  // Validate width
5927
6399
  if (typeof entryObj.width !== "number" || entryObj.width <= 0) {
@@ -5946,6 +6418,15 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5946
6418
  message: "Expected 'mimeType' to be a string, got '".concat(_typeof(entryObj.mimeType), "'"),
5947
6419
  value: entry
5948
6420
  });
6421
+ } else if (type === "videos" && entryObj.mimeType === HLS_MIME_TYPE) {
6422
+ // A stream is what the Studio made of an upload that WAS accepted, so
6423
+ // `accept` (which is about what may be picked) is not asked about it.
6424
+ if (!key.split("?")[0].toLowerCase().endsWith(".m3u8")) {
6425
+ errors.push({
6426
+ message: "An HLS video must be keyed by its master playlist (.m3u8). Got: ".concat(key),
6427
+ value: entry
6428
+ });
6429
+ }
5949
6430
  } else {
5950
6431
  var mimeTypeError = this.validateMediaMimeType(entryObj.mimeType, accept);
5951
6432
  if (mimeTypeError) {
@@ -5955,6 +6436,26 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5955
6436
  });
5956
6437
  }
5957
6438
  }
6439
+ if (type === "videos") {
6440
+ // The defaults every field picked from the set starts from, checked as
6441
+ // a field's own would be: they are the same choices, made once.
6442
+ var duration = typeof entryObj.duration === "number" && entryObj.duration > 0 ? entryObj.duration : undefined;
6443
+ errors.push.apply(errors, _toConsumableArray(validateTimes(entryObj, duration)).concat(_toConsumableArray(validateHotspot(entryObj)), _toConsumableArray(validatePoster(entryObj)), _toConsumableArray(validateCaptions(entryObj))));
6444
+ var misplaced = this.misplacedEntryFiles(key, entryObj);
6445
+ if (misplaced.length > 0) {
6446
+ var these = misplaced.length === 1 ? "this file is" : "".concat(misplaced.length, " files are");
6447
+ errors.push(this.mediaOptions.remote ? {
6448
+ message: "Expected the entry's files on Val Remote, but ".concat(these, " stored locally: ").concat(misplaced.join(", ")),
6449
+ // The key, as a local key's upload error carries: the fix
6450
+ // rewrites the entry under it.
6451
+ value: key,
6452
+ fixes: ["videos:upload-remote"]
6453
+ } : {
6454
+ message: "Expected the entry's files under ".concat(this.mediaOptions.dir, ", but ").concat(these, " remote: ").concat(misplaced.join(", ")),
6455
+ value: entry
6456
+ });
6457
+ }
6458
+ }
5958
6459
  if (type === "images") {
5959
6460
  // Validate hotspot if present
5960
6461
  if (entryObj.hotspot !== undefined) {
@@ -5966,7 +6467,8 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5966
6467
  });
5967
6468
  }
5968
6469
  }
5969
-
6470
+ }
6471
+ if (type === "images" || type === "videos") {
5970
6472
  // Validate alt using the alt schema
5971
6473
  var altPath = createValPathOfItem(path, "alt");
5972
6474
  if (altPath && altSchema) {
@@ -5981,16 +6483,59 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5981
6483
  }
5982
6484
  return false;
5983
6485
  }
6486
+
6487
+ /**
6488
+ * The poster and caption files of a set's entry that are not where the set
6489
+ * keeps its files. Asked only once the key itself is in place: a local key
6490
+ * in a remote set is reported (and uploaded) as the entry, files and all,
6491
+ * and a second error for its poster would be the same one twice.
6492
+ */
5984
6493
  }, {
5985
- key: "validateMediaMimeType",
5986
- value: function validateMediaMimeType(mimeType, accept) {
5987
- if (!mimeType.includes("/")) {
5988
- return "Invalid mime type format. Got: '".concat(mimeType, "'");
6494
+ key: "misplacedEntryFiles",
6495
+ value: function misplacedEntryFiles(key, entry) {
6496
+ var _this3 = this;
6497
+ if (!this.mediaOptions) {
6498
+ return [];
5989
6499
  }
5990
- if (!mimeTypeMatchesAccept(mimeType, accept)) {
5991
- return "Mime type mismatch. Found '".concat(mimeType, "' but schema accepts '").concat(accept, "'");
6500
+ var remote = this.mediaOptions.remote;
6501
+ if (this.isRemoteUrl(key) !== remote) {
6502
+ return [];
5992
6503
  }
5993
- return null;
6504
+ var files = [];
6505
+ var poster = entry.poster;
6506
+ if (isObject(poster) && typeof poster.path === "string") {
6507
+ files.push(poster.path);
6508
+ }
6509
+ if (Array.isArray(entry.captions)) {
6510
+ var _iterator4 = _createForOfIteratorHelper(entry.captions),
6511
+ _step4;
6512
+ try {
6513
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
6514
+ var track = _step4.value;
6515
+ if (isObject(track) && typeof track.path === "string") {
6516
+ files.push(track.path);
6517
+ }
6518
+ }
6519
+ } catch (err) {
6520
+ _iterator4.e(err);
6521
+ } finally {
6522
+ _iterator4.f();
6523
+ }
6524
+ }
6525
+ return files.filter(function (file) {
6526
+ return _this3.isRemoteUrl(file) !== remote;
6527
+ });
6528
+ }
6529
+ }, {
6530
+ key: "validateMediaMimeType",
6531
+ value: function validateMediaMimeType(mimeType, accept) {
6532
+ if (!mimeType.includes("/")) {
6533
+ return "Invalid mime type format. Got: '".concat(mimeType, "'");
6534
+ }
6535
+ if (!mimeTypeMatchesAccept(mimeType, accept)) {
6536
+ return "Mime type mismatch. Found '".concat(mimeType, "' but schema accepts '").concat(accept, "'");
6537
+ }
6538
+ return null;
5994
6539
  }
5995
6540
  }, {
5996
6541
  key: "executeAssert",
@@ -6344,6 +6889,9 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
6344
6889
  if (this.mediaOptions.encode !== undefined) {
6345
6890
  result.encode = this.mediaOptions.encode;
6346
6891
  }
6892
+ if (this.mediaOptions.stream !== undefined) {
6893
+ result.stream = this.mediaOptions.stream;
6894
+ }
6347
6895
  if (this.mediaOptions.altSchema) {
6348
6896
  result.alt = this.mediaOptions.altSchema["executeSerialize"]();
6349
6897
  }
@@ -10389,7 +10937,472 @@ var FileSchema = /*#__PURE__*/function (_Schema) {
10389
10937
  if (modulePaths.length === 0) {
10390
10938
  return null;
10391
10939
  }
10392
- return this.moduleMetadata[modulePaths[0]];
10940
+ return this.moduleMetadata[modulePaths[0]];
10941
+ }
10942
+ }, {
10943
+ key: "executeAssert",
10944
+ value: function executeAssert(path, src) {
10945
+ if (this.opt && src === null) {
10946
+ return {
10947
+ success: true,
10948
+ data: src
10949
+ };
10950
+ }
10951
+ if (src === null) {
10952
+ return {
10953
+ success: false,
10954
+ errors: _defineProperty({}, path, [{
10955
+ message: "Expected 'object', got 'null'",
10956
+ typeError: true
10957
+ }])
10958
+ };
10959
+ }
10960
+ if (_typeof(src) !== "object") {
10961
+ return {
10962
+ success: false,
10963
+ errors: _defineProperty({}, path, [{
10964
+ message: "Expected object, got '".concat(_typeof(src), "'"),
10965
+ typeError: true
10966
+ }])
10967
+ };
10968
+ }
10969
+ if (!("path" in src) || typeof src.path !== "string") {
10970
+ return {
10971
+ success: false,
10972
+ errors: _defineProperty({}, path, [{
10973
+ message: "A file must be an object with a 'path' (error type: missing_path)",
10974
+ typeError: true
10975
+ }])
10976
+ };
10977
+ }
10978
+ return {
10979
+ success: true,
10980
+ data: src
10981
+ };
10982
+ }
10983
+ }, {
10984
+ key: "nullable",
10985
+ value: function nullable() {
10986
+ return new FileSchema(this.options, true, this.isRemote, this.customValidateFunctions, this.moduleMetadata, this.isReadonly, this.isHidden, this.description, this.previewInput);
10987
+ }
10988
+ }, {
10989
+ key: "readonly",
10990
+ value: function readonly() {
10991
+ var isReadonly = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
10992
+ return new FileSchema(this.options, this.opt, this.isRemote, this.customValidateFunctions, this.moduleMetadata, isReadonly, this.isHidden, this.description, this.previewInput);
10993
+ }
10994
+ }, {
10995
+ key: "hidden",
10996
+ value: function hidden() {
10997
+ var isHidden = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
10998
+ return new FileSchema(this.options, this.opt, this.isRemote, this.customValidateFunctions, this.moduleMetadata, this.isReadonly, isHidden, this.description, this.previewInput);
10999
+ }
11000
+ }, {
11001
+ key: "executeCustomValidateAt",
11002
+ value: function executeCustomValidateAt(path, src) {
11003
+ return this.executeCustomValidateFunctions(src, this.customValidateFunctions, {
11004
+ path: path
11005
+ });
11006
+ }
11007
+
11008
+ /**
11009
+ * How this VALUE is shown where a preview of it is needed — a row in a
11010
+ * sortable list, a reference dropdown, a search hit. Never how the field
11011
+ * itself is edited (that is `render`). See `preview.ts`.
11012
+ *
11013
+ * @example
11014
+ * const schema = s.array(
11015
+ * s.file().preview(({ val }) => ({ title: val.path })),
11016
+ * );
11017
+ * export default c.define("/example.val.ts", schema, [
11018
+ * { path: "/public/val/example.pdf", mimeType: "application/pdf" },
11019
+ * ]);
11020
+ */
11021
+ }, {
11022
+ key: "preview",
11023
+ value: function preview(select) {
11024
+ return new FileSchema(this.options, this.opt, this.isRemote, this.customValidateFunctions, this.moduleMetadata, this.isReadonly, this.isHidden, this.description, select);
11025
+ }
11026
+ }, {
11027
+ key: "executePreviewItem",
11028
+ value: function executePreviewItem(src) {
11029
+ if (this.previewInput === null) {
11030
+ return null;
11031
+ }
11032
+ return this.previewInput({
11033
+ val: src
11034
+ });
11035
+ }
11036
+ }, {
11037
+ key: "declaresItemPreview",
11038
+ value: function declaresItemPreview() {
11039
+ return this.previewInput !== null;
11040
+ }
11041
+ }, {
11042
+ key: "executeSerialize",
11043
+ value: function executeSerialize() {
11044
+ var _this$customValidateF;
11045
+ var modulePaths = this.moduleMetadata ? Object.keys(this.moduleMetadata) : [];
11046
+ return {
11047
+ type: "file",
11048
+ preview: this.previewInput ? true : undefined,
11049
+ options: this.options,
11050
+ opt: this.opt,
11051
+ remote: this.isRemote,
11052
+ customValidate: this.customValidateFunctions && ((_this$customValidateF = this.customValidateFunctions) === null || _this$customValidateF === void 0 ? void 0 : _this$customValidateF.length) > 0,
11053
+ referencedModule: modulePaths.length > 0 ? modulePaths[0] : undefined,
11054
+ readonly: this.isReadonly,
11055
+ hidden: this.isHidden,
11056
+ description: this.description
11057
+ };
11058
+ }
11059
+ }]);
11060
+ }(Schema);
11061
+
11062
+ /**
11063
+ * A file picked from a gallery. Its mime type lives in the gallery, so the
11064
+ * field carries only the path.
11065
+ */
11066
+
11067
+ /** A file of its own, carrying its own mime type. */
11068
+
11069
+ function file(options) {
11070
+ return fileOf(false, options);
11071
+ }
11072
+
11073
+ /**
11074
+ * `s.file()` for a project whose media is all remote (`files.remote` in
11075
+ * `val.config.ts`): the same schema, with `.remote()` already applied. See
11076
+ * `initSchema`.
11077
+ */
11078
+
11079
+ function remoteFile(options) {
11080
+ return fileOf(true, options);
11081
+ }
11082
+ function fileOf(remote, options) {
11083
+ var isModule = !!options && !!Internal.getValPath(options);
11084
+ if (isModule) {
11085
+ var allModules = {};
11086
+ for (var _i = 0, _arr = [options]; _i < _arr.length; _i++) {
11087
+ var valModule = _arr[_i];
11088
+ var modulePath = getValPath(valModule);
11089
+ if (modulePath === undefined) {
11090
+ throw new Error("Invalid argument passed to s.file(). Expected a ValModule constructed through c.define, but got an object without a valid module path.");
11091
+ }
11092
+ allModules[modulePath] = getSource(valModule);
11093
+ }
11094
+ return new FileSchema({}, false, remote, [], allModules);
11095
+ }
11096
+ return new FileSchema(options, false, remote);
11097
+ }
11098
+
11099
+ /**
11100
+ * Turn an upload into an HLS stream, in the browser, before it is uploaded.
11101
+ *
11102
+ * The Studio transcodes the picked file with WebCodecs into one rendition per
11103
+ * entry of `renditions` (never upscaled: a 720p upload gets no 1080p
11104
+ * rendition), segments each into CMAF and writes the playlists. What is stored
11105
+ * is a directory beside the other uploads; the field's `path` is its master
11106
+ * playlist. See `architecture/media.md`.
11107
+ *
11108
+ * `type` is required for the same reason it is on `ImageEncodeOptions`: a
11109
+ * second kind of stream would then be additive.
11110
+ */
11111
+
11112
+ /** `false` (or absent) uploads the file as it was picked. */
11113
+
11114
+ /**
11115
+ * What a SET-BACKED field (`s.video(videosVal)`) may say for itself.
11116
+ *
11117
+ * Not `VideoOptions`: `dir` and `accept` belong to the set. `stream` is the
11118
+ * exception, as `encode` is for an image: it is about what happens to an
11119
+ * upload on its way in, so a field that wants the file as it was picked,
11120
+ * where its set streams, has to be able to say `stream: false`.
11121
+ */
11122
+
11123
+ /**
11124
+ * The set a field picks from: its entries as they were when the module was
11125
+ * evaluated (the published set — see `GalleryImageSource` for why that is
11126
+ * enough), and whether it stores its videos on Val Remote.
11127
+ */
11128
+
11129
+ var DEFAULT_VIDEO_ACCEPT = "video/*";
11130
+ var DEFAULT_VIDEO_RENDITIONS = [1080, 720, 480];
11131
+ var DEFAULT_VIDEO_SEGMENT_DURATION = 6;
11132
+ var VideoSchema = /*#__PURE__*/function (_Schema) {
11133
+ function VideoSchema(options) {
11134
+ var _this;
11135
+ var opt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
11136
+ var isRemote = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
11137
+ var customValidateFunctions = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];
11138
+ var isReadonly = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
11139
+ var isHidden = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false;
11140
+ var description = arguments.length > 6 ? arguments[6] : undefined;
11141
+ var renderInput = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : null;
11142
+ var previewInput = arguments.length > 8 && arguments[8] !== undefined ? arguments[8] : null;
11143
+ var gallery = arguments.length > 9 && arguments[9] !== undefined ? arguments[9] : null;
11144
+ _classCallCheck(this, VideoSchema);
11145
+ _this = _callSuper(this, VideoSchema);
11146
+ _this.options = options;
11147
+ _this.opt = opt;
11148
+ _this.isRemote = isRemote;
11149
+ _this.customValidateFunctions = customValidateFunctions;
11150
+ _this.isReadonly = isReadonly;
11151
+ _this.isHidden = isHidden;
11152
+ _this.description = description;
11153
+ _this.renderInput = renderInput;
11154
+ _this.previewInput = previewInput;
11155
+ _this.gallery = gallery;
11156
+ return _this;
11157
+ }
11158
+
11159
+ /**
11160
+ * Describe this field.
11161
+ *
11162
+ * The description is INPUT HELP: it is shown where this field's value is
11163
+ * entered — beside its input in the Val editor, and for a record's key
11164
+ * schema in every form that asks for a key — so it is where you say what an
11165
+ * editor needs to know to fill it in RIGHT, which the field name cannot
11166
+ * carry. It is not a name for the value: that is `.preview(...)`, and it is
11167
+ * read somewhere else. The description also travels in the serialized
11168
+ * schema, which is what the AI assistant and the MCP tools read.
11169
+ *
11170
+ * Pass `null` to clear a description set earlier.
11171
+ *
11172
+ * @example
11173
+ * const schema = s.video().describe("Plays muted behind the headline");
11174
+ * export default c.define("/example.val.ts", schema, {
11175
+ * path: "/public/val/example.mp4",
11176
+ * mimeType: "video/mp4",
11177
+ * });
11178
+ */
11179
+ _inherits(VideoSchema, _Schema);
11180
+ return _createClass(VideoSchema, [{
11181
+ key: "describe",
11182
+ value: function describe(description) {
11183
+ return new VideoSchema(this.options, this.opt, this.isRemote, this.customValidateFunctions, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.renderInput, this.previewInput, this.gallery);
11184
+ }
11185
+
11186
+ /**
11187
+ * Store the video on Val's remote content host instead of in your
11188
+ * repository.
11189
+ *
11190
+ * The bytes still go into the patch store when the video is uploaded — the
11191
+ * push to the remote host happens at publish. What changes is where the
11192
+ * published video lives: `path` (and the poster's, and each caption track's)
11193
+ * becomes a remote URL rather than a path under `/public`, so the repository
11194
+ * does not grow with every upload. Upload remote videos in the Studio.
11195
+ *
11196
+ * @example
11197
+ * const schema = s.video().remote();
11198
+ * export default c.define("/example.val.ts", schema, {
11199
+ * path: "https://remote.val.build/file/p/my-project/b/01/v/1.0.0/h/8f2a1c/f/3b9d70/p/public/val/example.mp4",
11200
+ * mimeType: "video/mp4",
11201
+ * });
11202
+ */
11203
+ }, {
11204
+ key: "remote",
11205
+ value: function remote() {
11206
+ return new VideoSchema(this.options, this.opt, true, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput, this.gallery);
11207
+ }
11208
+
11209
+ /**
11210
+ * Add a custom validation rule to this field.
11211
+ *
11212
+ * The function is called with the field's value and returns `false` when the
11213
+ * value is fine, or a STRING with the message to show when it is not. Call it
11214
+ * more than once to add more rules — they all run, and every message is
11215
+ * reported.
11216
+ *
11217
+ * Write the check as a ternary, not as `ok || "message"`: that returns `true`
11218
+ * when the value is fine, and `true` is not one of the two answers.
11219
+ *
11220
+ * Validation runs in the Studio as you type, in `npx val validate` and
11221
+ * before a publish.
11222
+ *
11223
+ * The second argument carries the `path` of the field being validated, for
11224
+ * when the message needs to say where the problem is.
11225
+ *
11226
+ * @example
11227
+ * const schema = s.video().validate((val) =>
11228
+ * (val.duration ?? 0) <= 30 ? false : "Keep it under 30 seconds",
11229
+ * );
11230
+ * export default c.define("/example.val.ts", schema, {
11231
+ * path: "/public/val/example.mp4",
11232
+ * mimeType: "video/mp4",
11233
+ * duration: 12,
11234
+ * });
11235
+ */
11236
+ }, {
11237
+ key: "validate",
11238
+ value: function validate(validationFunction) {
11239
+ return new VideoSchema(this.options, this.opt, this.isRemote, [].concat(_toConsumableArray(this.customValidateFunctions), [validationFunction]), this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput, this.gallery);
11240
+ }
11241
+ }, {
11242
+ key: "executeValidate",
11243
+ value: function executeValidate(path, src) {
11244
+ var customValidationErrors = this.executeCustomValidateFunctions(src, this.customValidateFunctions, {
11245
+ path: path
11246
+ });
11247
+ var report = function report(errors) {
11248
+ var all = [].concat(_toConsumableArray(customValidationErrors), _toConsumableArray(errors));
11249
+ return all.length > 0 ? _defineProperty({}, path, all) : false;
11250
+ };
11251
+ if (src === null || src === undefined) {
11252
+ if (this.opt) {
11253
+ return report([]);
11254
+ }
11255
+ return report([{
11256
+ message: "Non-optional video was null or undefined.",
11257
+ value: src
11258
+ }]);
11259
+ }
11260
+ if (_typeof(src) !== "object" || typeof src.path !== "string") {
11261
+ return report([{
11262
+ message: "A video must be an object with a 'path' string.",
11263
+ value: src
11264
+ }]);
11265
+ }
11266
+ if (this.gallery) {
11267
+ return report(this.validateFromGallery(src, this.gallery));
11268
+ }
11269
+ return report(this.validateOwn(src));
11270
+ }
11271
+
11272
+ /**
11273
+ * A set-backed field: the file and what was read from it are the set's,
11274
+ * so the field is checked for what is its own, and for pointing at an
11275
+ * entry the set has.
11276
+ */
11277
+ }, {
11278
+ key: "validateFromGallery",
11279
+ value: function validateFromGallery(src, gallery) {
11280
+ var own = ownMetadataOf(src);
11281
+ var repeated = ["mimeType", "width", "height", "duration"].filter(function (key) {
11282
+ return own[key] !== undefined;
11283
+ });
11284
+ if (repeated.length > 0) {
11285
+ return [{
11286
+ message: "A video from a set must not carry its own ".concat(repeated.join(", "), ": ").concat(repeated.length === 1 ? "it is" : "they are", " stored in the set."),
11287
+ value: src
11288
+ }];
11289
+ }
11290
+ var entry = gallery.entries === null ? null : gallery.entries[src.path];
11291
+ if (entry === undefined) {
11292
+ return [{
11293
+ message: "The set does not have a video at '".concat(src.path, "'."),
11294
+ value: src
11295
+ }];
11296
+ }
11297
+ // The poster and the captions are the field's own files, and live where
11298
+ // the set keeps its videos. The video itself is the set's to move.
11299
+ var misplaced = filesOfVideoSource(src).slice(1).filter(function (file) {
11300
+ return isRemoteMediaPath(file) !== gallery.remote;
11301
+ });
11302
+ if (misplaced.length > 0) {
11303
+ return [misplacedError(src, misplaced, gallery.remote)];
11304
+ }
11305
+ var duration = entry !== null && typeof entry.duration === "number" && entry.duration > 0 ? entry.duration : undefined;
11306
+ return [].concat(_toConsumableArray(validateTimes(src, duration)), _toConsumableArray(validateMixedTimes(src, entry)), _toConsumableArray(validateHotspot(src)), _toConsumableArray(validatePoster(src)), _toConsumableArray(validateCaptions(src)));
11307
+ }
11308
+
11309
+ /** A video of its own: it carries the file, and what was read from it. */
11310
+ }, {
11311
+ key: "validateOwn",
11312
+ value: function validateOwn(src) {
11313
+ var _this2 = this;
11314
+ var own = ownMetadataOf(src);
11315
+ // Every file the video names has to be where the schema says, and one fix
11316
+ // moves them all — a video on the content host with its poster still in
11317
+ // the repository is half a migration, not a choice. The error is reported
11318
+ // once for the whole video, never per file.
11319
+ var misplaced = filesOfVideoSource(src).filter(function (file) {
11320
+ return isRemoteMediaPath(file) !== _this2.isRemote;
11321
+ });
11322
+ if (misplaced.length > 0) {
11323
+ return [misplacedError(src, misplaced, this.isRemote)];
11324
+ }
11325
+ var errors = [];
11326
+ var isHls = isHlsVideo({
11327
+ path: src.path,
11328
+ mimeType: stringOrUndefined(own.mimeType)
11329
+ });
11330
+ var extensionMimeType = filenameToMimeType(stripQuery(src.path));
11331
+ if (isHls) {
11332
+ if (!stripQuery(src.path).toLowerCase().endsWith(".m3u8")) {
11333
+ errors.push({
11334
+ message: "An HLS video's path must be its master playlist (.m3u8). Got: ".concat(src.path),
11335
+ value: src
11336
+ });
11337
+ }
11338
+ } else if (!extensionMimeType || !extensionMimeType.startsWith("video/")) {
11339
+ errors.push({
11340
+ message: "Could not tell this is a video from its file extension. Got: ".concat(src.path),
11341
+ value: src
11342
+ });
11343
+ } else if (own.mimeType !== undefined && extensionMimeType !== own.mimeType) {
11344
+ errors.push({
11345
+ message: "Mime type and file extension not matching. Mime type is '".concat(String(own.mimeType), "' but file extension is '").concat(extensionMimeType, "'"),
11346
+ value: src
11347
+ });
11348
+ }
11349
+ if (own.mimeType !== undefined && typeof own.mimeType !== "string") {
11350
+ errors.push({
11351
+ message: "'mimeType' must be a string.",
11352
+ value: src
11353
+ });
11354
+ } else if (own.mimeType !== undefined && !isHls) {
11355
+ var _this$options$accept, _this$options;
11356
+ var accept = (_this$options$accept = (_this$options = this.options) === null || _this$options === void 0 ? void 0 : _this$options.accept) !== null && _this$options$accept !== void 0 ? _this$options$accept : DEFAULT_VIDEO_ACCEPT;
11357
+ if (!mimeTypeMatchesAccept(own.mimeType, accept)) {
11358
+ errors.push({
11359
+ message: "Mime type mismatch. Found '".concat(own.mimeType, "' but schema accepts '").concat(accept, "'"),
11360
+ value: src
11361
+ });
11362
+ }
11363
+ }
11364
+ var _iterator = _createForOfIteratorHelper(["width", "height", "duration"]),
11365
+ _step;
11366
+ try {
11367
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
11368
+ var key = _step.value;
11369
+ var value = own[key];
11370
+ if (value !== undefined && (typeof value !== "number" || !Number.isFinite(value) || value <= 0)) {
11371
+ errors.push({
11372
+ message: "'".concat(key, "' must be a positive number. Got: ").concat(JSON.stringify(value)),
11373
+ value: src
11374
+ });
11375
+ }
11376
+ }
11377
+ } catch (err) {
11378
+ _iterator.e(err);
11379
+ } finally {
11380
+ _iterator.f();
11381
+ }
11382
+ var duration = typeof own.duration === "number" && own.duration > 0 ? own.duration : undefined;
11383
+ errors.push.apply(errors, _toConsumableArray(validateTimes(src, duration)));
11384
+ errors.push.apply(errors, _toConsumableArray(validateHotspot(src)));
11385
+ errors.push.apply(errors, _toConsumableArray(validatePoster(src)));
11386
+ errors.push.apply(errors, _toConsumableArray(validateCaptions(src)));
11387
+ if (errors.length > 0) {
11388
+ return errors;
11389
+ }
11390
+
11391
+ // Everything authored is fine. What is left is what is read from the
11392
+ // bytes, which this package cannot do — so it is handed on as a fix. The
11393
+ // CLI reads a local file's headers from disk and a remote one's over
11394
+ // HTTP, with Range requests, so neither is downloaded whole.
11395
+ var missing = ["mimeType", "width", "height", "duration"].filter(function (key) {
11396
+ return own[key] === undefined;
11397
+ });
11398
+ if (missing.length > 0) {
11399
+ return [{
11400
+ message: "Video metadata is missing: ".concat(missing.join(", "), "."),
11401
+ value: src,
11402
+ fixes: ["video:add-metadata"]
11403
+ }];
11404
+ }
11405
+ return [];
10393
11406
  }
10394
11407
  }, {
10395
11408
  key: "executeAssert",
@@ -10422,7 +11435,7 @@ var FileSchema = /*#__PURE__*/function (_Schema) {
10422
11435
  return {
10423
11436
  success: false,
10424
11437
  errors: _defineProperty({}, path, [{
10425
- message: "A file must be an object with a 'path' (error type: missing_path)",
11438
+ message: "A video must be an object with a 'path' (error type: missing_path)",
10426
11439
  typeError: true
10427
11440
  }])
10428
11441
  };
@@ -10435,19 +11448,19 @@ var FileSchema = /*#__PURE__*/function (_Schema) {
10435
11448
  }, {
10436
11449
  key: "nullable",
10437
11450
  value: function nullable() {
10438
- return new FileSchema(this.options, true, this.isRemote, this.customValidateFunctions, this.moduleMetadata, this.isReadonly, this.isHidden, this.description, this.previewInput);
11451
+ return new VideoSchema(this.options, true, this.isRemote, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput, this.gallery);
10439
11452
  }
10440
11453
  }, {
10441
11454
  key: "readonly",
10442
11455
  value: function readonly() {
10443
11456
  var isReadonly = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
10444
- return new FileSchema(this.options, this.opt, this.isRemote, this.customValidateFunctions, this.moduleMetadata, isReadonly, this.isHidden, this.description, this.previewInput);
11457
+ return new VideoSchema(this.options, this.opt, this.isRemote, this.customValidateFunctions, isReadonly, this.isHidden, this.description, this.renderInput, this.previewInput, this.gallery);
10445
11458
  }
10446
11459
  }, {
10447
11460
  key: "hidden",
10448
11461
  value: function hidden() {
10449
11462
  var isHidden = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
10450
- return new FileSchema(this.options, this.opt, this.isRemote, this.customValidateFunctions, this.moduleMetadata, this.isReadonly, isHidden, this.description, this.previewInput);
11463
+ return new VideoSchema(this.options, this.opt, this.isRemote, this.customValidateFunctions, this.isReadonly, isHidden, this.description, this.renderInput, this.previewInput, this.gallery);
10451
11464
  }
10452
11465
  }, {
10453
11466
  key: "executeCustomValidateAt",
@@ -10457,6 +11470,25 @@ var FileSchema = /*#__PURE__*/function (_Schema) {
10457
11470
  });
10458
11471
  }
10459
11472
 
11473
+ /**
11474
+ * How this field is laid out in the editor when it is the item of an array
11475
+ * or record: `{ as: "inline" }` renders the field itself inside each row,
11476
+ * instead of a preview row that navigates to it.
11477
+ *
11478
+ * Static configuration, not a callback — see `render.ts`.
11479
+ *
11480
+ * @example
11481
+ * const schema = s.array(s.video().render({ as: "inline" }));
11482
+ * export default c.define("/example.val.ts", schema, [
11483
+ * { path: "/public/val/example.mp4", mimeType: "video/mp4" },
11484
+ * ]);
11485
+ */
11486
+ }, {
11487
+ key: "render",
11488
+ value: function render(input) {
11489
+ return new VideoSchema(this.options, this.opt, this.isRemote, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, input, this.previewInput, this.gallery);
11490
+ }
11491
+
10460
11492
  /**
10461
11493
  * How this VALUE is shown where a preview of it is needed — a row in a
10462
11494
  * sortable list, a reference dropdown, a search hit. Never how the field
@@ -10464,16 +11496,19 @@ var FileSchema = /*#__PURE__*/function (_Schema) {
10464
11496
  *
10465
11497
  * @example
10466
11498
  * const schema = s.array(
10467
- * s.file().preview(({ val }) => ({ title: val.path })),
11499
+ * s.video().preview(({ val }) => ({
11500
+ * title: val.alt ?? val.path,
11501
+ * image: val.poster,
11502
+ * })),
10468
11503
  * );
10469
11504
  * export default c.define("/example.val.ts", schema, [
10470
- * { path: "/public/val/example.pdf", mimeType: "application/pdf" },
11505
+ * { path: "/public/val/example.mp4", mimeType: "video/mp4" },
10471
11506
  * ]);
10472
11507
  */
10473
11508
  }, {
10474
11509
  key: "preview",
10475
11510
  value: function preview(select) {
10476
- return new FileSchema(this.options, this.opt, this.isRemote, this.customValidateFunctions, this.moduleMetadata, this.isReadonly, this.isHidden, this.description, select);
11511
+ return new VideoSchema(this.options, this.opt, this.isRemote, this.customValidateFunctions, this.isReadonly, this.isHidden, this.description, this.renderInput, select, this.gallery);
10477
11512
  }
10478
11513
  }, {
10479
11514
  key: "executePreviewItem",
@@ -10493,16 +11528,16 @@ var FileSchema = /*#__PURE__*/function (_Schema) {
10493
11528
  }, {
10494
11529
  key: "executeSerialize",
10495
11530
  value: function executeSerialize() {
10496
- var _this$customValidateF;
10497
- var modulePaths = this.moduleMetadata ? Object.keys(this.moduleMetadata) : [];
11531
+ var _this$renderInput, _this$customValidateF, _this$gallery;
10498
11532
  return {
10499
- type: "file",
11533
+ type: "video",
11534
+ render: (_this$renderInput = this.renderInput) !== null && _this$renderInput !== void 0 ? _this$renderInput : undefined,
10500
11535
  preview: this.previewInput ? true : undefined,
10501
11536
  options: this.options,
10502
11537
  opt: this.opt,
10503
11538
  remote: this.isRemote,
10504
11539
  customValidate: this.customValidateFunctions && ((_this$customValidateF = this.customValidateFunctions) === null || _this$customValidateF === void 0 ? void 0 : _this$customValidateF.length) > 0,
10505
- referencedModule: modulePaths.length > 0 ? modulePaths[0] : undefined,
11540
+ referencedModule: (_this$gallery = this.gallery) === null || _this$gallery === void 0 ? void 0 : _this$gallery.modulePath,
10506
11541
  readonly: this.isReadonly,
10507
11542
  hidden: this.isHidden,
10508
11543
  description: this.description
@@ -10512,27 +11547,137 @@ var FileSchema = /*#__PURE__*/function (_Schema) {
10512
11547
  }(Schema);
10513
11548
 
10514
11549
  /**
10515
- * A file picked from a gallery. Its mime type lives in the gallery, so the
10516
- * field carries only the path.
11550
+ * Every file a video value names: the video, its poster and each caption
11551
+ * track. An HLS stream's segments are not here — they are named by its
11552
+ * playlists, not by the value.
10517
11553
  */
10518
-
10519
- /** A file of its own, carrying its own mime type. */
10520
-
10521
- function file(options) {
10522
- var isModule = !!options && !!Internal.getValPath(options);
10523
- if (isModule) {
10524
- var allModules = {};
10525
- for (var _i = 0, _arr = [options]; _i < _arr.length; _i++) {
10526
- var valModule = _arr[_i];
10527
- var modulePath = getValPath(valModule);
10528
- if (modulePath === undefined) {
10529
- throw new Error("Invalid argument passed to s.file(). Expected a ValModule constructed through c.define, but got an object without a valid module path.");
11554
+ function filesOfVideoSource(src) {
11555
+ var files = [src.path];
11556
+ if (src.poster && _typeof(src.poster) === "object" && typeof src.poster.path === "string") {
11557
+ files.push(src.poster.path);
11558
+ }
11559
+ if (Array.isArray(src.captions)) {
11560
+ var _iterator2 = _createForOfIteratorHelper(src.captions),
11561
+ _step2;
11562
+ try {
11563
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
11564
+ var track = _step2.value;
11565
+ if (track && _typeof(track) === "object" && typeof track.path === "string") {
11566
+ files.push(track.path);
11567
+ }
10530
11568
  }
10531
- allModules[modulePath] = getSource(valModule);
11569
+ } catch (err) {
11570
+ _iterator2.e(err);
11571
+ } finally {
11572
+ _iterator2.f();
10532
11573
  }
10533
- return new FileSchema({}, false, false, [], allModules);
10534
11574
  }
10535
- return new FileSchema(options);
11575
+ return files;
11576
+ }
11577
+
11578
+ /**
11579
+ * What the value says about its file, typed as what it IS rather than what
11580
+ * it should be: a set-backed field's type has none of these, and is checked
11581
+ * for carrying them anyway, because hand-written JSON never saw the type.
11582
+ */
11583
+ function ownMetadataOf(src) {
11584
+ var value = _objectSpread2({}, src);
11585
+ return {
11586
+ mimeType: value.mimeType,
11587
+ width: value.width,
11588
+ height: value.height,
11589
+ duration: value.duration
11590
+ };
11591
+ }
11592
+
11593
+ /**
11594
+ * A start the field set against an end the set holds, or the reverse: each is
11595
+ * fine alone, and the field's own times and the set's are each checked where
11596
+ * they are written, so what is left is the pair the page will actually play.
11597
+ */
11598
+ function validateMixedTimes(src, entry) {
11599
+ var _src$startTime, _src$endTime;
11600
+ if (entry === null || src.startTime === undefined === (src.endTime === undefined)) {
11601
+ return [];
11602
+ }
11603
+ var start = (_src$startTime = src.startTime) !== null && _src$startTime !== void 0 ? _src$startTime : entry.startTime;
11604
+ var end = (_src$endTime = src.endTime) !== null && _src$endTime !== void 0 ? _src$endTime : entry.endTime;
11605
+ if (typeof start !== "number" || typeof end !== "number" || start < end) {
11606
+ return [];
11607
+ }
11608
+ var whose = src.startTime === undefined ? "the start is the set's" : "the end is the set's";
11609
+ return [{
11610
+ message: "'startTime' (".concat(start, "s) must be before 'endTime' (").concat(end, "s) \u2014 ").concat(whose, "."),
11611
+ value: src
11612
+ }];
11613
+ }
11614
+ function stringOrUndefined(value) {
11615
+ return typeof value === "string" ? value : undefined;
11616
+ }
11617
+ function misplacedError(src, misplaced, shouldBeRemote) {
11618
+ var these = misplaced.length === 1 ? "this file is" : "".concat(misplaced.length, " files are");
11619
+ return shouldBeRemote ? {
11620
+ message: "Expected a remote video, but ".concat(these, " stored locally: ").concat(misplaced.join(", ")),
11621
+ value: src,
11622
+ fixes: ["video:upload-remote"]
11623
+ } : {
11624
+ message: "Expected a local video (files under /public), but ".concat(these, " remote: ").concat(misplaced.join(", ")),
11625
+ value: src,
11626
+ fixes: ["video:download-remote"]
11627
+ };
11628
+ }
11629
+ function stripQuery(path) {
11630
+ return path.split("?")[0];
11631
+ }
11632
+
11633
+ /**
11634
+ * A video picked from a set (`s.videoset()`). What is true of the file —
11635
+ * its type, size and length — lives in the set; the field carries what one
11636
+ * page chose about it.
11637
+ */
11638
+
11639
+ /**
11640
+ * Define a video: an `.mp4` / `.webm` file, or an HLS stream the Studio makes
11641
+ * from an upload when `stream` is set.
11642
+ */
11643
+
11644
+ function video(options, galleryOptions) {
11645
+ return videoOf(options, galleryOptions);
11646
+ }
11647
+
11648
+ /**
11649
+ * `s.video()` for a project whose media is all remote (`files.remote` in
11650
+ * `val.config.ts`): the same schema, with `.remote()` already applied. See
11651
+ * `initSchema`.
11652
+ */
11653
+
11654
+ function remoteVideo(options, galleryOptions) {
11655
+ return videoOf(options, galleryOptions).remote();
11656
+ }
11657
+ function videoOf(options, galleryOptions) {
11658
+ var _options$GetSchema, _setSchema$remote, _setSchema$remote2;
11659
+ if (options === undefined || !isVideosetModule(options)) {
11660
+ return new VideoSchema(options);
11661
+ }
11662
+ var modulePath = getValPath(options);
11663
+ if (modulePath === undefined) {
11664
+ throw new Error("Invalid argument passed to s.video(). Expected a ValModule constructed through c.define, but got an object without a valid module path.");
11665
+ }
11666
+ var setSchema = (_options$GetSchema = options[GetSchema]) === null || _options$GetSchema === void 0 ? void 0 : _options$GetSchema["executeSerialize"]();
11667
+ if ((setSchema === null || setSchema === void 0 ? void 0 : setSchema.type) !== "record" || setSchema.mediaType !== "videos") {
11668
+ var _setSchema$mediaType, _setSchema$type;
11669
+ throw new Error("s.video(".concat(modulePath, "): the module must be an s.videoset(). Got ").concat((setSchema === null || setSchema === void 0 ? void 0 : setSchema.type) === "record" ? "a record of ".concat((_setSchema$mediaType = setSchema.mediaType) !== null && _setSchema$mediaType !== void 0 ? _setSchema$mediaType : "values") : (_setSchema$type = setSchema === null || setSchema === void 0 ? void 0 : setSchema.type) !== null && _setSchema$type !== void 0 ? _setSchema$type : "no schema", "."));
11670
+ }
11671
+ return new VideoSchema((galleryOptions === null || galleryOptions === void 0 ? void 0 : galleryOptions.stream) !== undefined ? {
11672
+ stream: galleryOptions.stream
11673
+ } : undefined, false, (_setSchema$remote = setSchema.remote) !== null && _setSchema$remote !== void 0 ? _setSchema$remote : false, [], false, false, undefined, null, null, {
11674
+ modulePath: modulePath,
11675
+ entries: getSource(options),
11676
+ remote: (_setSchema$remote2 = setSchema.remote) !== null && _setSchema$remote2 !== void 0 ? _setSchema$remote2 : false
11677
+ });
11678
+ }
11679
+ function isVideosetModule(value) {
11680
+ return GetSchema in value;
10536
11681
  }
10537
11682
 
10538
11683
  /**
@@ -11514,6 +12659,99 @@ var imageset = function imageset(options) {
11514
12659
  });
11515
12660
  };
11516
12661
 
12662
+ /**
12663
+ * Options for s.videoset()
12664
+ */
12665
+
12666
+ /** See `ImagesetAltOption`: `alt` is optional only when it is the default. */
12667
+
12668
+ /**
12669
+ * One video of a set, keyed by its file path (an HLS stream by its master
12670
+ * playlist).
12671
+ *
12672
+ * Two kinds of thing are here. What is TRUE OF THE FILE — what it is, how
12673
+ * big, how long — is read from the bytes and is the set's alone. The rest —
12674
+ * the description, focal point, poster, start and end, and captions — is what
12675
+ * a page chooses about a video, and an entry holds it as the DEFAULT: every
12676
+ * field picked from the set (`s.video(videosVal)`) starts from it and may
12677
+ * override it key by key, so a video described and trimmed once is described
12678
+ * and trimmed everywhere it is used. `fillFromGallery` is the one merge.
12679
+ *
12680
+ * The poster is also the set's thumbnail of the entry: there is no separate
12681
+ * still for the gallery, because two stills of one video would disagree.
12682
+ */
12683
+
12684
+ /**
12685
+ * Define a collection of videos — the video twin of `s.imageset()`.
12686
+ *
12687
+ * A field picks from it with `s.video(videosVal)`. An entry may hold the
12688
+ * description, poster, start and end, focal point and captions as defaults,
12689
+ * and a field overrides any of them by setting its own. Uploading in such a
12690
+ * field adds to the set. Call `.remote()` on the result
12691
+ * to store the set on Val Remote.
12692
+ *
12693
+ * @example
12694
+ * ```typescript
12695
+ * const schema = s.videoset({
12696
+ * dir: "/public/val/videos",
12697
+ * stream: { type: "hls" },
12698
+ * });
12699
+ * export default c.define("/content/videos.val.ts", schema, {
12700
+ * "/public/val/videos/intro_51df2.mp4": {
12701
+ * mimeType: "video/mp4",
12702
+ * width: 1280,
12703
+ * height: 720,
12704
+ * duration: 12.5,
12705
+ * alt: "The team, introducing itself",
12706
+ * startTime: 1.5,
12707
+ * },
12708
+ * });
12709
+ * ```
12710
+ */
12711
+ var videoset = function videoset(options) {
12712
+ var _options$alt, _options$accept;
12713
+ // The same assertion as in `imageset()`, and sound for the same reason:
12714
+ // `VideosetAltOption` makes `alt` required unless `Alt` is the default.
12715
+ var altSchema = (_options$alt = options.alt) !== null && _options$alt !== void 0 ? _options$alt : string().nullable();
12716
+ var itemSchema = new ObjectSchema({
12717
+ mimeType: new StringSchema({}, false),
12718
+ width: new NumberSchema(undefined, false),
12719
+ height: new NumberSchema(undefined, false),
12720
+ duration: new NumberSchema(undefined, false),
12721
+ alt: altSchema
12722
+ }, false);
12723
+ return new RecordSchema(itemSchema, false, [], null, null, {
12724
+ type: "videos",
12725
+ accept: (_options$accept = options.accept) !== null && _options$accept !== void 0 ? _options$accept : DEFAULT_VIDEO_ACCEPT,
12726
+ dir: options.dir,
12727
+ remote: false,
12728
+ altSchema: altSchema,
12729
+ stream: options.stream
12730
+ });
12731
+ };
12732
+
12733
+ /**
12734
+ * The video schema a set's entry is hashed against, for a remote ref.
12735
+ *
12736
+ * The set's item schema is an object, but a remote ref's validation hash is
12737
+ * computed over a MEDIA schema, so one is synthesized from the set — the one
12738
+ * definition the CLI's upload (`videos:upload-remote`) and the Studio's (the
12739
+ * set's gallery) both use, because two copies are two hashes for the same
12740
+ * file. `stream` is left out: it says what is done to an upload on its way
12741
+ * in, not whether the bytes that arrived are valid.
12742
+ */
12743
+ function videosetEntryVideoSchema(set) {
12744
+ return {
12745
+ type: "video",
12746
+ opt: false,
12747
+ options: _objectSpread2(_objectSpread2({}, set.accept ? {
12748
+ accept: set.accept
12749
+ } : {}), set.dir ? {
12750
+ dir: set.dir
12751
+ } : {})
12752
+ };
12753
+ }
12754
+
11517
12755
  // import type { F } from "ts-toolbelt";
11518
12756
  // import { i18n, I18n } from "./schema/future/i18n";
11519
12757
  // import { oneOf } from "./schema/future/oneOf";
@@ -11521,7 +12759,31 @@ var imageset = function imageset(options) {
11521
12759
  // export type InitSchemaLocalized<Locales extends readonly string[]> = {
11522
12760
  // readonly i18n: I18n<Locales>;
11523
12761
  // };
11524
- function initSchema() {
12762
+
12763
+ function initSchema(options) {
12764
+ var media = (options === null || options === void 0 ? void 0 : options.remote) === true ? {
12765
+ image: remoteImage,
12766
+ file: remoteFile,
12767
+ video: remoteVideo,
12768
+ richtext: remoteRichtext,
12769
+ imageset: function (options) {
12770
+ return imageset(options).remote();
12771
+ },
12772
+ fileset: function (options) {
12773
+ return fileset(options).remote();
12774
+ },
12775
+ videoset: function (options) {
12776
+ return videoset(options).remote();
12777
+ }
12778
+ } : {
12779
+ image: image,
12780
+ file: file,
12781
+ video: video,
12782
+ richtext: richtext,
12783
+ imageset: imageset,
12784
+ fileset: fileset,
12785
+ videoset: videoset
12786
+ };
11525
12787
  // locales: F.Narrow<Locales>
11526
12788
  return {
11527
12789
  string: string,
@@ -11533,14 +12795,15 @@ function initSchema() {
11533
12795
  "enum": enumSchema,
11534
12796
  union: union,
11535
12797
  // oneOf,
11536
- richtext: richtext,
11537
- image: image,
12798
+ richtext: media.richtext,
12799
+ image: media.image,
11538
12800
  literal: literal,
11539
12801
  keyOf: keyOf,
11540
12802
  view: view,
11541
12803
  record: record,
11542
- file: file,
11543
- fileset: fileset,
12804
+ file: media.file,
12805
+ video: media.video,
12806
+ fileset: media.fileset,
11544
12807
  date: date,
11545
12808
  datetime: datetime,
11546
12809
  color: color,
@@ -11548,7 +12811,8 @@ function initSchema() {
11548
12811
  locale: locale,
11549
12812
  route: route,
11550
12813
  router: router,
11551
- imageset: imageset,
12814
+ imageset: media.imageset,
12815
+ videoset: media.videoset,
11552
12816
  settings: settings
11553
12817
  // i18n: i18n(locales),
11554
12818
  };
@@ -11566,8 +12830,11 @@ function initSchema() {
11566
12830
 
11567
12831
  // TODO: Rename to createValSystem (only to be used by internal things), we can then export * from '@valbuild/core' in the next package then.
11568
12832
  var initVal = function initVal(config) {
12833
+ var _config$files;
11569
12834
  // const locales = options?.locales;
11570
- var s = initSchema();
12835
+ var s = initSchema({
12836
+ remote: (config === null || config === void 0 || (_config$files = config.files) === null || _config$files === void 0 ? void 0 : _config$files.remote) === true
12837
+ });
11571
12838
  // if (locales?.required) {
11572
12839
  // console.error("Locales / i18n currently not implemented");
11573
12840
  // return {
@@ -12357,6 +13624,43 @@ function computeSourcesSha(entries) {
12357
13624
  }
12358
13625
  return sourcesSha;
12359
13626
  }
13627
+
13628
+ /**
13629
+ * The `schemaSha` fold on its own: path and serialized schema, in `val.modules`
13630
+ * order.
13631
+ *
13632
+ * Separate for the same reason as {@link computeSourcesSha}: the Studio folds
13633
+ * the schemas of the bundle it is running and compares the answer with the
13634
+ * `schemaSha` on `/stat`, which is how it learns that a new schema has been
13635
+ * deployed under it. One implementation, so that comparison is between two
13636
+ * runs of the same code.
13637
+ */
13638
+ function computeSchemaSha(entries) {
13639
+ var schemaSha = "";
13640
+ var _iterator2 = _createForOfIteratorHelper(entries),
13641
+ _step2;
13642
+ try {
13643
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
13644
+ var _step2$value = _step2.value,
13645
+ path = _step2$value.path,
13646
+ serializedSchema = _step2$value.serializedSchema;
13647
+ // The PATH is part of the schema set, not just the schema: renaming a
13648
+ // module while leaving its schema byte-identical still changes which paths
13649
+ // exist, and an open client keyed its schema cache by the old path. Hashing
13650
+ // the schema alone left this SHA unchanged, so with no commitSha to fall
13651
+ // back on the client never refetched /schema.
13652
+ schemaSha = hash(schemaSha + JSON.stringify({
13653
+ path: path,
13654
+ schema: serializedSchema
13655
+ }));
13656
+ }
13657
+ } catch (err) {
13658
+ _iterator2.e(err);
13659
+ } finally {
13660
+ _iterator2.f();
13661
+ }
13662
+ return schemaSha;
13663
+ }
12360
13664
  function computeValModuleShas(config, entries, moduleErrors) {
12361
13665
  var configSha = hash(JSON.stringify(config));
12362
13666
  var sourcesSha = computeSourcesSha(entries);
@@ -12369,12 +13673,12 @@ function computeValModuleShas(config, entries, moduleErrors) {
12369
13673
  // `gitCommit: process.env.VERCEL_GIT_COMMIT_SHA` / `gitBranch`, which are
12370
13674
  // server-only env vars and therefore `undefined` in the browser. Seeding with
12371
13675
  // them made the two sides disagree on every production load.
12372
- var schemaSha = "";
12373
- var _iterator2 = _createForOfIteratorHelper(entries),
12374
- _step2;
13676
+ var schemaSha = computeSchemaSha(entries);
13677
+ var _iterator3 = _createForOfIteratorHelper(entries),
13678
+ _step3;
12375
13679
  try {
12376
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
12377
- var entry = _step2.value;
13680
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
13681
+ var entry = _step3.value;
12378
13682
  var path = entry.path,
12379
13683
  source = entry.source,
12380
13684
  serializedSchema = entry.serializedSchema;
@@ -12384,20 +13688,11 @@ function computeValModuleShas(config, entries, moduleErrors) {
12384
13688
  source: source,
12385
13689
  modulesErrors: moduleErrors.slice(0, entry.moduleErrorsAt)
12386
13690
  }));
12387
- // The PATH is part of the schema set, not just the schema: renaming a
12388
- // module while leaving its schema byte-identical still changes which paths
12389
- // exist, and an open client keyed its schema cache by the old path. Hashing
12390
- // the schema alone left this SHA unchanged, so with no commitSha to fall
12391
- // back on the client never refetched /schema.
12392
- schemaSha = hash(schemaSha + JSON.stringify({
12393
- path: path,
12394
- schema: serializedSchema
12395
- }));
12396
13691
  }
12397
13692
  } catch (err) {
12398
- _iterator2.e(err);
13693
+ _iterator3.e(err);
12399
13694
  } finally {
12400
- _iterator2.f();
13695
+ _iterator3.f();
12401
13696
  }
12402
13697
  return {
12403
13698
  baseSha: baseSha,
@@ -12419,7 +13714,7 @@ function extractValModules(_x) {
12419
13714
  }
12420
13715
  function _extractValModules() {
12421
13716
  _extractValModules = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(valModules) {
12422
- var moduleErrors, addModuleError, sources, schemas, serializedSchemas, shaEntries, moduleIdx, module, at, value, path, schema, source, serializedSchema, pathM, _iterator3, _step3, error, _iterator4, _step4, _error, _t, _t2;
13717
+ var moduleErrors, addModuleError, sources, schemas, serializedSchemas, shaEntries, moduleIdx, module, at, value, path, schema, source, serializedSchema, pathM, _iterator4, _step4, error, _iterator5, _step5, _error, _t, _t2;
12423
13718
  return _regenerator().w(function (_context) {
12424
13719
  while (1) switch (_context.p = _context.n) {
12425
13720
  case 0:
@@ -12566,10 +13861,10 @@ function _extractValModules() {
12566
13861
  // Appended AFTER the loop on purpose: each entry's `moduleErrorsAt` is the
12567
13862
  // error count at the time it was folded in, so the SHAs stay a function of
12568
13863
  // the modules themselves.
12569
- _iterator3 = _createForOfIteratorHelper(resolveSettingsModule(serializedSchemas).errors);
13864
+ _iterator4 = _createForOfIteratorHelper(resolveSettingsModule(serializedSchemas).errors);
12570
13865
  try {
12571
- for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
12572
- error = _step3.value;
13866
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
13867
+ error = _step4.value;
12573
13868
  moduleErrors.push(error);
12574
13869
  }
12575
13870
  // Same kind of rule, same reason it is here: a view cycle is a property of the
@@ -12577,20 +13872,20 @@ function _extractValModules() {
12577
13872
  // catches it either — a view stores a pointer, not content, so there is no
12578
13873
  // data cycle for a source walk to trip over.
12579
13874
  } catch (err) {
12580
- _iterator3.e(err);
13875
+ _iterator4.e(err);
12581
13876
  } finally {
12582
- _iterator3.f();
13877
+ _iterator4.f();
12583
13878
  }
12584
- _iterator4 = _createForOfIteratorHelper(viewCycleErrors(serializedSchemas));
13879
+ _iterator5 = _createForOfIteratorHelper(viewCycleErrors(serializedSchemas));
12585
13880
  try {
12586
- for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
12587
- _error = _step4.value;
13881
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
13882
+ _error = _step5.value;
12588
13883
  moduleErrors.push(_error);
12589
13884
  }
12590
13885
  } catch (err) {
12591
- _iterator4.e(err);
13886
+ _iterator5.e(err);
12592
13887
  } finally {
12593
- _iterator4.f();
13888
+ _iterator5.f();
12594
13889
  }
12595
13890
  return _context.a(2, _objectSpread2(_objectSpread2({
12596
13891
  sources: sources,
@@ -12640,7 +13935,7 @@ function _extractValModules() {
12640
13935
  * instead would reintroduce exactly the silent skip described above.
12641
13936
  */
12642
13937
  function hasRemoteFileSchema(schema) {
12643
- if (schema.type === "file" || schema.type === "image") {
13938
+ if (schema.type === "file" || schema.type === "image" || schema.type === "video") {
12644
13939
  return !!schema.remote;
12645
13940
  } else if (schema.type === "richtext") {
12646
13941
  var _schema$options;
@@ -12719,7 +14014,7 @@ function hasRemoteFileSchema(schema) {
12719
14014
  * already has a data-loss incident attached to it (see above).
12720
14015
  */
12721
14016
  function hasMediaSchema(schema) {
12722
- if (schema.type === "file" || schema.type === "image") {
14017
+ if (schema.type === "file" || schema.type === "image" || schema.type === "video") {
12723
14018
  return true;
12724
14019
  } else if (schema.type === "richtext") {
12725
14020
  var _schema$options2;
@@ -13085,6 +14380,10 @@ function getValidationBasis(coreVersion, schema, fileExt, metadata, fileHash) {
13085
14380
  // bytes that arrived are valid. Leaving it in would mean that changing a
13086
14381
  // quality setting re-validates every remote file in the project.
13087
14382
  delete options.encode;
14383
+ // `stream` likewise: it is what the Studio made of an upload (an HLS
14384
+ // directory instead of one file), and every file it made is validated on its
14385
+ // own merits.
14386
+ delete options.stream;
13088
14387
  var schemaValidationBasis = {
13089
14388
  type: schema.type,
13090
14389
  opt: schema.opt,
@@ -13104,6 +14403,93 @@ function getFileHash(text) {
13104
14403
  return hashToRemoteFileHash(getSHA256Hash(new Uint8Array(text)));
13105
14404
  }
13106
14405
 
14406
+ var cache$1 = new WeakMap();
14407
+
14408
+ /**
14409
+ * The PUBLISHED entries of every gallery a module's fields pick from — an
14410
+ * `s.image(galleryVal)`, `s.file(filesVal)` or `s.video(videosVal)` — by the
14411
+ * gallery's module path.
14412
+ *
14413
+ * A gallery-backed value names its file and nothing else that is the
14414
+ * gallery's, so a reader has to look the entry up (`fillFromGallery`). In
14415
+ * draft mode the reader has every module's draft source to look in; outside
14416
+ * it, nothing — and the page got a set-backed video with no type, size,
14417
+ * poster or description, and an image with no dimensions. But the field's
14418
+ * schema was built from the gallery module (`s.video(videosVal)` reads it at
14419
+ * definition), so the published entries are already here, on the schema
14420
+ * instance. This is where they are read from.
14421
+ *
14422
+ * Stops at a view: the module it points at is read on its own, with its own
14423
+ * galleries.
14424
+ */
14425
+ function galleriesOf(schema) {
14426
+ if (schema === undefined) {
14427
+ return new Map();
14428
+ }
14429
+ var cached = cache$1.get(schema);
14430
+ if (cached) {
14431
+ return cached;
14432
+ }
14433
+ var galleries = new Map();
14434
+ var seen = new Set();
14435
+ function walk(node) {
14436
+ if (_typeof(node) !== "object" || node === null || seen.has(node)) {
14437
+ return;
14438
+ }
14439
+ seen.add(node);
14440
+ if (node instanceof ValViewSchema) {
14441
+ return;
14442
+ }
14443
+ if (node instanceof ImageSchema) {
14444
+ for (var _i = 0, _Object$entries = Object.entries(node["moduleMetadata"]); _i < _Object$entries.length; _i++) {
14445
+ var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
14446
+ modulePath = _Object$entries$_i[0],
14447
+ entries = _Object$entries$_i[1];
14448
+ galleries.set(modulePath, entries);
14449
+ }
14450
+ return;
14451
+ }
14452
+ if (node instanceof FileSchema) {
14453
+ for (var _i2 = 0, _Object$entries2 = Object.entries(node["moduleMetadata"]); _i2 < _Object$entries2.length; _i2++) {
14454
+ var _Object$entries2$_i = _slicedToArray(_Object$entries2[_i2], 2),
14455
+ _modulePath = _Object$entries2$_i[0],
14456
+ _entries = _Object$entries2$_i[1];
14457
+ galleries.set(_modulePath, _entries);
14458
+ }
14459
+ return;
14460
+ }
14461
+ if (node instanceof VideoSchema) {
14462
+ var gallery = node["gallery"];
14463
+ if (gallery && gallery.entries !== null) {
14464
+ galleries.set(gallery.modulePath, gallery.entries);
14465
+ }
14466
+ return;
14467
+ }
14468
+ if (Array.isArray(node)) {
14469
+ var _iterator = _createForOfIteratorHelper(node),
14470
+ _step;
14471
+ try {
14472
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
14473
+ var item = _step.value;
14474
+ walk(item);
14475
+ }
14476
+ } catch (err) {
14477
+ _iterator.e(err);
14478
+ } finally {
14479
+ _iterator.f();
14480
+ }
14481
+ return;
14482
+ }
14483
+ for (var _i3 = 0, _Object$values = Object.values(node); _i3 < _Object$values.length; _i3++) {
14484
+ var value = _Object$values[_i3];
14485
+ walk(value);
14486
+ }
14487
+ }
14488
+ walk(schema);
14489
+ cache$1.set(schema, galleries);
14490
+ return galleries;
14491
+ }
14492
+
13107
14493
  /**
13108
14494
  * The variant of a discriminated union that a value takes, by its tag.
13109
14495
  *
@@ -13309,7 +14695,7 @@ function deserializeSchema(serialized) {
13309
14695
  return schema;
13310
14696
  }
13311
14697
  function deserializeSchemaImpl(serialized) {
13312
- var _serialized$options, _serialized$options2, _serialized$multiline, _serialized$render, _serialized$accept, _serialized$dir, _serialized$remote, _serialized$jsonValue, _serialized$render2, _serialized$external, _serialized$render3, _serialized$readonly, _serialized$hidden;
14698
+ var _serialized$options, _serialized$options2, _serialized$multiline, _serialized$render, _serialized$accept, _serialized$dir, _serialized$remote, _serialized$jsonValue, _serialized$render2, _serialized$external, _serialized$render3, _serialized$render4, _serialized$remote2, _serialized$readonly, _serialized$hidden;
13313
14699
  switch (serialized.type) {
13314
14700
  case "string":
13315
14701
  return new StringSchema(_objectSpread2(_objectSpread2({}, serialized.options), {}, {
@@ -13368,6 +14754,8 @@ function deserializeSchemaImpl(serialized) {
13368
14754
  accept: (_serialized$accept = serialized.accept) !== null && _serialized$accept !== void 0 ? _serialized$accept : "*/*",
13369
14755
  dir: (_serialized$dir = serialized.dir) !== null && _serialized$dir !== void 0 ? _serialized$dir : "/public/val",
13370
14756
  remote: (_serialized$remote = serialized.remote) !== null && _serialized$remote !== void 0 ? _serialized$remote : false,
14757
+ encode: serialized.encode,
14758
+ stream: serialized.stream,
13371
14759
  altSchema: serialized.alt ? deserializeSchema(serialized.alt) : undefined
13372
14760
  } : undefined, false, false, serialized.description, (_serialized$jsonValue = serialized.jsonValues) !== null && _serialized$jsonValue !== void 0 ? _serialized$jsonValue : false,
13373
14761
  // The preview closure cannot survive serialization; the render can.
@@ -13393,6 +14781,12 @@ function deserializeSchemaImpl(serialized) {
13393
14781
  return new FileSchema(serialized.options, serialized.opt, serialized.remote, [], {}, false, false, serialized.description);
13394
14782
  case "image":
13395
14783
  return new ImageSchema(serialized.options, serialized.opt, serialized.remote, [], {}, false, false, serialized.description);
14784
+ case "video":
14785
+ return new VideoSchema(serialized.options, serialized.opt, serialized.remote, [], false, false, serialized.description, (_serialized$render4 = serialized.render) !== null && _serialized$render4 !== void 0 ? _serialized$render4 : null, null, serialized.referencedModule ? {
14786
+ modulePath: serialized.referencedModule,
14787
+ entries: null,
14788
+ remote: (_serialized$remote2 = serialized.remote) !== null && _serialized$remote2 !== void 0 ? _serialized$remote2 : false
14789
+ } : null);
13396
14790
  case "date":
13397
14791
  return new DateSchema(serialized.options, serialized.opt, [], false, false, serialized.description);
13398
14792
  case "dateTime":
@@ -14088,7 +15482,7 @@ function isPageRouter(routerId) {
14088
15482
 
14089
15483
  var corePackageJson = {
14090
15484
  name: "@valbuild/core",
14091
- version: "0.139.0",
15485
+ version: "0.141.0",
14092
15486
  "private": false,
14093
15487
  description: "Val - supercharged hard-coded content",
14094
15488
  repository: {
@@ -14177,6 +15571,7 @@ var Internal = {
14177
15571
  VERSION: {
14178
15572
  core: corePackageJson.version
14179
15573
  },
15574
+ videosetEntryVideoSchema: videosetEntryVideoSchema,
14180
15575
  // A view's read-path plumbing: `Internal` rather than the public surface,
14181
15576
  // because an app never builds or unwraps a handle — it passes one to
14182
15577
  // `useVal`.
@@ -14187,9 +15582,15 @@ var Internal = {
14187
15582
  viewModulesOf: viewModulesOf,
14188
15583
  mediaUrl: mediaUrl,
14189
15584
  resolveMedia: resolveMedia,
15585
+ resolveVideo: resolveVideo,
14190
15586
  isRemoteMediaPath: isRemoteMediaPath,
14191
15587
  media: {
14192
- fillFromGallery: fillFromGallery
15588
+ fillFromGallery: fillFromGallery,
15589
+ galleriesOf: galleriesOf,
15590
+ galleryEntryOf: galleryEntryOf,
15591
+ GALLERY_DEFAULT_GROUPS: GALLERY_DEFAULT_GROUPS,
15592
+ isHlsVideo: isHlsVideo,
15593
+ HLS_MIME_TYPE: HLS_MIME_TYPE
14193
15594
  },
14194
15595
  getSchema: getSchema,
14195
15596
  getValPath: getValPath,
@@ -14237,6 +15638,13 @@ var Internal = {
14237
15638
  isExternalFileRef: isExternalFileRef,
14238
15639
  createValPathOfItem: createValPathOfItem,
14239
15640
  getSHA256Hash: getSHA256Hash,
15641
+ /**
15642
+ * The same hash as `getSHA256Hash`, fed a chunk at a time: what a caller
15643
+ * hashing hundreds of megabytes uses to give the thread back between chunks.
15644
+ */
15645
+ createSHA256Hash: function createSHA256Hash() {
15646
+ return createHash("sha256");
15647
+ },
14240
15648
  initSchema: initSchema,
14241
15649
  getMimeType: getMimeType,
14242
15650
  mimeTypeToFileExt: mimeTypeToFileExt,
@@ -14295,4 +15703,4 @@ function tryJsonParse(str) {
14295
15703
  }
14296
15704
  }
14297
15705
 
14298
- export { DEFAULT_COLOR_FORMAT as $, ArraySchema as A, BooleanSchema as B, isRootModuleFilePath as C, DEFAULT_CONTENT_HOST as D, resolveSettingsModule as E, FATAL_ERROR_TYPES as F, GenericSelector as G, localeTagError as H, Internal as I, localeOfValue as J, declaredKeySetOf as K, LocaleSchema as L, ModuleFilePathSep as M, NumberSchema as N, ObjectSchema as O, PatchError as P, missingDeclaredKeys as Q, RecordSchema as R, Schema as S, THEME_LOGO_DIRECTORY as T, discriminatedUnionBranchOf as U, VAL_EXTENSION as V, ColorSchema as W, CodeSchema as X, CODE_LANGUAGES as Y, COLOR_FORMATS as Z, _typeof as _, _slicedToArray as a, DateTimeSchema as a0, KeyOfSchema as a1, ValViewSchema as a2, isValViewSource as a3, RouteSchema as a4, RichTextSchema as a5, DiscriminatedUnionSchema as a6, EnumSchema as a7, LiteralSchema as a8, deserializeSchema as a9, previewScope as aa, isInlineRender as ab, parseNextJsRoutePattern as ac, parseTanStackRoutePattern as ad, tanStackSegmentsOfRoutePath as ae, validateUrlAgainstPattern as af, getSourcePathFromRoute as ag, isPageRouter as ah, describeSchemeRejection as ai, isCheckableScheme as aj, rejectScheme as ak, schemeOf as al, UNSAFE_URL_SCHEMES as am, _createClass as b, _classCallCheck as c, _toConsumableArray as d, DEFAULT_APP_HOST as e, DEFAULT_STATIC_HOST as f, extractValModules as g, computeValModuleShas as h, initVal as i, computeSourcesSha as j, hasRemoteFileSchema as k, hasMediaSchema as l, modules as m, DEFAULT_VAL_REMOTE_HOST as n, derefPatch as o, StringSchema as p, ImageSchema as q, FileSchema as r, splitModuleFilePathAndModulePath as s, DateSchema as t, SettingsSchema as u, ASSISTANT_SETTINGS_MAX_LENGTH as v, THEME_RADIUS_STEPS as w, THEME_RADIUS_LENGTHS as x, assistantAvailability as y, SETTINGS_MODULE_CONVENTION as z };
15706
+ export { discriminatedUnionBranchOf as $, ArraySchema as A, BooleanSchema as B, ASSISTANT_SETTINGS_MAX_LENGTH as C, DEFAULT_CONTENT_HOST as D, THEME_RADIUS_STEPS as E, FATAL_ERROR_TYPES as F, GenericSelector as G, THEME_RADIUS_LENGTHS as H, Internal as I, assistantAvailability as J, SETTINGS_MODULE_CONVENTION as K, isRootModuleFilePath as L, ModuleFilePathSep as M, NumberSchema as N, ObjectSchema as O, PatchError as P, resolveSettingsModule as Q, RecordSchema as R, Schema as S, THEME_LOGO_DIRECTORY as T, localeTagError as U, VAL_EXTENSION as V, localeOfValue as W, LocaleSchema as X, declaredKeySetOf as Y, missingDeclaredKeys as Z, _typeof as _, _slicedToArray as a, ColorSchema as a0, CodeSchema as a1, CODE_LANGUAGES as a2, COLOR_FORMATS as a3, DEFAULT_COLOR_FORMAT as a4, DateTimeSchema as a5, KeyOfSchema as a6, ValViewSchema as a7, isValViewSource as a8, RouteSchema as a9, RichTextSchema as aa, DiscriminatedUnionSchema as ab, EnumSchema as ac, LiteralSchema as ad, deserializeSchema as ae, previewScope as af, isInlineRender as ag, parseNextJsRoutePattern as ah, parseTanStackRoutePattern as ai, tanStackSegmentsOfRoutePath as aj, validateUrlAgainstPattern as ak, getSourcePathFromRoute as al, isPageRouter as am, describeSchemeRejection as an, isCheckableScheme as ao, rejectScheme as ap, schemeOf as aq, UNSAFE_URL_SCHEMES as ar, _createClass as b, _classCallCheck as c, _toConsumableArray as d, DEFAULT_APP_HOST as e, DEFAULT_STATIC_HOST as f, extractValModules as g, computeValModuleShas as h, initVal as i, computeSourcesSha as j, computeSchemaSha as k, hasRemoteFileSchema as l, modules as m, hasMediaSchema as n, DEFAULT_VAL_REMOTE_HOST as o, derefPatch as p, StringSchema as q, ImageSchema as r, splitModuleFilePathAndModulePath as s, FileSchema as t, VideoSchema as u, DEFAULT_VIDEO_ACCEPT as v, DEFAULT_VIDEO_RENDITIONS as w, DEFAULT_VIDEO_SEGMENT_DURATION as x, DateSchema as y, SettingsSchema as z };