@valbuild/core 0.121.0 → 0.124.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.
@@ -1962,7 +1962,7 @@ var union = function union(key) {
1962
1962
  return new UnionSchema(key, objects);
1963
1963
  };
1964
1964
 
1965
- var RegEx = /^(https?:\/\/[^/]+)\/file\/p\/([^/]+)\/b\/([^/]+)\/v\/([^/]+)\/h\/([^/]+)\/f\/([^/]+)\/p\/(.+)$/;
1965
+ var RegEx$1 = /^(https?:\/\/[^/]+)\/file\/p\/([^/]+)\/b\/([^/]+)\/v\/([^/]+)\/h\/([^/]+)\/f\/([^/]+)\/p\/(.+)$/;
1966
1966
  function splitRemoteRef(ref) {
1967
1967
  if (ref[0] === "/") {
1968
1968
  return {
@@ -1970,7 +1970,7 @@ function splitRemoteRef(ref) {
1970
1970
  error: "Not a remote ref: " + ref
1971
1971
  };
1972
1972
  }
1973
- var match = ref.match(RegEx);
1973
+ var match = ref.match(RegEx$1);
1974
1974
  if (!match) {
1975
1975
  return {
1976
1976
  status: "error",
@@ -4368,6 +4368,94 @@ function _resolveJsonValues() {
4368
4368
  return _resolveJsonValues.apply(this, arguments);
4369
4369
  }
4370
4370
 
4371
+ /**
4372
+ * The string used as the `_type` discriminator of an {@link ExternalRecordSrc}.
4373
+ */
4374
+ var EXTERNAL_VAL_EXTENSION_TAG = "external";
4375
+
4376
+ /**
4377
+ * Type-level-only slots. Declared here rather than reusing `PhantomType`
4378
+ * (`source/index.ts`) because three separate facts have to ride along, and that
4379
+ * helper carries one.
4380
+ *
4381
+ * None of them exist at runtime: `c.external()` produces `{ _type: "external" }`
4382
+ * and nothing else.
4383
+ */
4384
+
4385
+ /**
4386
+ * The source of a record whose entries live behind an adapter — a database, an
4387
+ * HTTP API, a bucket — instead of in the module.
4388
+ *
4389
+ * This is what `c.external()` returns, and it is the counterpart of the
4390
+ * `c.json(() => import(...))` marker a `.jsonValues()` entry carries: a value
4391
+ * that says "the content is not here" without saying anything about what is.
4392
+ * The schema says *which* adapter (`.external("posts")`); this says *that* the
4393
+ * entries are elsewhere, which is what every source-walking consumer needs to
4394
+ * know without holding a schema.
4395
+ *
4396
+ * Three phantom parameters, each earning its place:
4397
+ *
4398
+ * - `T` — the item type. Without it `c.define` would return a module whose type
4399
+ * is "a marker", the item type would be gone, and an adapter could not be
4400
+ * typed from the schema at all.
4401
+ * - `L` — the label from `.external(label)`, so a binding can be checked against
4402
+ * the schema that declared it.
4403
+ * - `RO` — whether the schema said `.readonly()`, which decides whether the
4404
+ * adapter must implement writes or is forbidden from doing so.
4405
+ *
4406
+ * The defaults are the WIDEST instantiation on purpose: the bare name appears in
4407
+ * the `Source` and `SelectorSource` unions, and every narrower instantiation has
4408
+ * to be assignable to it. The `external()` factory below defaults the other way,
4409
+ * exactly as `JsonSource<T = unknown>` and `json<T = Json>()` do.
4410
+ */
4411
+
4412
+ /**
4413
+ * The marker written as a module's source when its record is `.external()`.
4414
+ *
4415
+ * Takes no arguments: everything it carries is inferred from the schema it is
4416
+ * checked against in `c.define`. The assertion mirrors `json()` — the phantom
4417
+ * slots are symbol-keyed and have no runtime representation, so there is nothing
4418
+ * to construct for them.
4419
+ */
4420
+ function external() {
4421
+ return _defineProperty({}, VAL_EXTENSION, EXTERNAL_VAL_EXTENSION_TAG);
4422
+ }
4423
+ function isExternal(obj) {
4424
+ return _typeof(obj) === "object" && obj !== null && VAL_EXTENSION in obj && obj[VAL_EXTENSION] === EXTERNAL_VAL_EXTENSION_TAG;
4425
+ }
4426
+
4427
+ /**
4428
+ * The item type an external record holds, read back off a module.
4429
+ *
4430
+ * `GenericSelector` is not imported here — that would be a cycle through
4431
+ * `../selector` — so this is written against the phantom slot directly and the
4432
+ * selector-shaped overloads live where the selector types do.
4433
+ */
4434
+
4435
+ /** The label an external record was declared with. */
4436
+
4437
+ /** Whether the schema behind an external record said `.readonly()`. */
4438
+
4439
+ /**
4440
+ * The source of an external record as it may be WRITTEN.
4441
+ *
4442
+ * Either the marker, or entries inline in the `.val.ts`. Inline entries are
4443
+ * accepted by the type on purpose, for the same reason `.jsonValues()` accepts
4444
+ * them (see `JsonValuesRecordSrc`): pasting content straight into the module is
4445
+ * the natural first thing to write, and a type error there is a dead end — the
4446
+ * author cannot see what to write instead. Validation reports the inline entries
4447
+ * as an `external:upload` fix instead, so the mistake is repaired rather than
4448
+ * blocking authoring, and they are readable at runtime meanwhile.
4449
+ */
4450
+
4451
+ /**
4452
+ * Is this module source an external record that still has entries written
4453
+ * inline? Used by validation to raise `external:upload`.
4454
+ */
4455
+ function hasInlineExternalEntries(source) {
4456
+ return _typeof(source) === "object" && source !== null && !Array.isArray(source) && !isExternal(source) && Object.keys(source).length > 0;
4457
+ }
4458
+
4371
4459
  /**
4372
4460
  * The source type of a `.jsonValues()` record: every entry value is EITHER a
4373
4461
  * lazily loaded {@link JsonSource} whose resolved content is the (loosened, see
@@ -4396,6 +4484,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
4396
4484
  var isJsonValues = arguments.length > 9 && arguments[9] !== undefined ? arguments[9] : false;
4397
4485
  var previewInput = arguments.length > 10 && arguments[10] !== undefined ? arguments[10] : null;
4398
4486
  var renderInput = arguments.length > 11 && arguments[11] !== undefined ? arguments[11] : null;
4487
+ var externalLabel = arguments.length > 12 && arguments[12] !== undefined ? arguments[12] : null;
4399
4488
  _classCallCheck(this, RecordSchema);
4400
4489
  _this = _callSuper(this, RecordSchema);
4401
4490
  _this.item = item;
@@ -4410,24 +4499,28 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
4410
4499
  _this.isJsonValues = isJsonValues;
4411
4500
  _this.previewInput = previewInput;
4412
4501
  _this.renderInput = renderInput;
4502
+ _this.externalLabel = externalLabel;
4413
4503
  return _this;
4414
4504
  }
4415
4505
  _inherits(RecordSchema, _Schema);
4416
4506
  return _createClass(RecordSchema, [{
4417
4507
  key: "describe",
4418
4508
  value: function describe(description) {
4419
- return new RecordSchema(this.item, this.opt, this.customValidateFunctions, this.currentRouter, this.keySchema, this.mediaOptions, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.isJsonValues, this.previewInput, this.renderInput);
4509
+ return new RecordSchema(this.item, this.opt, this.customValidateFunctions, this.currentRouter, this.keySchema, this.mediaOptions, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined, this.isJsonValues, this.previewInput, this.renderInput, this.externalLabel);
4420
4510
  }
4421
4511
  }, {
4422
4512
  key: "validate",
4423
4513
  value: function validate(validationFunction) {
4424
- return new RecordSchema(this.item, this.opt, [].concat(_toConsumableArray(this.customValidateFunctions), [validationFunction]), this.currentRouter, this.keySchema, this.mediaOptions, this.isReadonly, this.isHidden, this.description, this.isJsonValues, this.previewInput, this.renderInput);
4514
+ return new RecordSchema(this.item, this.opt, [].concat(_toConsumableArray(this.customValidateFunctions), [validationFunction]), this.currentRouter, this.keySchema, this.mediaOptions, this.isReadonly, this.isHidden, this.description, this.isJsonValues, this.previewInput, this.renderInput, this.externalLabel);
4425
4515
  }
4426
4516
  }, {
4427
4517
  key: "executeValidate",
4428
4518
  value: function executeValidate(path, src) {
4429
4519
  var _this2 = this;
4430
4520
  var error = false;
4521
+ if (this.externalLabel !== null) {
4522
+ return this.validateExternal(path, src);
4523
+ }
4431
4524
  var customValidationErrors = this.executeCustomValidateFunctions(src, this.customValidateFunctions, {
4432
4525
  path: path
4433
4526
  });
@@ -4764,31 +4857,42 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
4764
4857
  }, {
4765
4858
  key: "nullable",
4766
4859
  value: function nullable() {
4767
- return new RecordSchema(this.item, true, this.customValidateFunctions, this.currentRouter, this.keySchema, this.mediaOptions, this.isReadonly, this.isHidden, this.description, this.isJsonValues, this.previewInput, this.renderInput);
4860
+ return new RecordSchema(this.item, true, this.customValidateFunctions, this.currentRouter, this.keySchema, this.mediaOptions, this.isReadonly, this.isHidden, this.description, this.isJsonValues, this.previewInput, this.renderInput, this.externalLabel);
4768
4861
  }
4862
+
4863
+ /**
4864
+ * A record you can look at but not change.
4865
+ *
4866
+ * The flag rides in the TYPE as well as in the instance, so `.external()` can
4867
+ * freeze it into the source marker and the adapter's write methods become
4868
+ * required or forbidden on the strength of it. That is also why `.readonly()`
4869
+ * must come BEFORE `.external()`: afterwards the value is a `Schema`, and
4870
+ * there is no `.readonly()` left to call — which is the error the compiler
4871
+ * gives, and a clearer one than a runtime throw.
4872
+ */
4769
4873
  }, {
4770
4874
  key: "readonly",
4771
4875
  value: function readonly() {
4772
4876
  var isReadonly = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
4773
- return new RecordSchema(this.item, this.opt, this.customValidateFunctions, this.currentRouter, this.keySchema, this.mediaOptions, isReadonly, this.isHidden, this.description, this.isJsonValues, this.previewInput, this.renderInput);
4877
+ return new RecordSchema(this.item, this.opt, this.customValidateFunctions, this.currentRouter, this.keySchema, this.mediaOptions, isReadonly, this.isHidden, this.description, this.isJsonValues, this.previewInput, this.renderInput, this.externalLabel);
4774
4878
  }
4775
4879
  }, {
4776
4880
  key: "hidden",
4777
4881
  value: function hidden() {
4778
4882
  var isHidden = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
4779
- return new RecordSchema(this.item, this.opt, this.customValidateFunctions, this.currentRouter, this.keySchema, this.mediaOptions, this.isReadonly, isHidden, this.description, this.isJsonValues, this.previewInput, this.renderInput);
4883
+ return new RecordSchema(this.item, this.opt, this.customValidateFunctions, this.currentRouter, this.keySchema, this.mediaOptions, this.isReadonly, isHidden, this.description, this.isJsonValues, this.previewInput, this.renderInput, this.externalLabel);
4780
4884
  }
4781
4885
  }, {
4782
4886
  key: "router",
4783
4887
  value: function router(_router) {
4784
- return new RecordSchema(this.item, this.opt, this.customValidateFunctions, _router, this.keySchema, this.mediaOptions, this.isReadonly, this.isHidden, this.description, this.isJsonValues, this.previewInput, this.renderInput);
4888
+ return new RecordSchema(this.item, this.opt, this.customValidateFunctions, _router, this.keySchema, this.mediaOptions, this.isReadonly, this.isHidden, this.description, this.isJsonValues, this.previewInput, this.renderInput, this.externalLabel);
4785
4889
  }
4786
4890
  }, {
4787
4891
  key: "remote",
4788
4892
  value: function remote() {
4789
4893
  return new RecordSchema(this.item, this.opt, this.customValidateFunctions, this.currentRouter, this.keySchema, this.mediaOptions ? _objectSpread2(_objectSpread2({}, this.mediaOptions), {}, {
4790
4894
  remote: true
4791
- }) : undefined, this.isReadonly, this.isHidden, this.description, this.isJsonValues, this.previewInput, this.renderInput);
4895
+ }) : undefined, this.isReadonly, this.isHidden, this.description, this.isJsonValues, this.previewInput, this.renderInput, this.externalLabel);
4792
4896
  }
4793
4897
 
4794
4898
  /**
@@ -4830,7 +4934,84 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
4830
4934
  // Empty by construction: the guard above rejects any that were registered.
4831
4935
  [], this.currentRouter, this.keySchema, this.mediaOptions, this.isReadonly, this.isHidden, this.description, true,
4832
4936
  // Null by construction: the guard above rejects any that was declared.
4833
- null, this.renderInput);
4937
+ null, this.renderInput, this.externalLabel);
4938
+ }
4939
+
4940
+ /**
4941
+ * Store this record's entries behind an adapter — a database, an HTTP API, a
4942
+ * bucket — instead of in the module.
4943
+ *
4944
+ * Available on every record-derived schema, which is `s.record()`,
4945
+ * `s.router()`, `s.images()` and `s.files()` alike: a router is a
4946
+ * `RecordSchema` with a `ValRouter`, a gallery one with media options, so all
4947
+ * four get external storage from this one method.
4948
+ *
4949
+ * `label` does not address the binding — the registry is keyed by module, and
4950
+ * so is the wire protocol. It exists so a reader of the schema can see what
4951
+ * backs it, and it is checked against the binding that claims it, so the two
4952
+ * cannot drift.
4953
+ *
4954
+ * Only supported on a module's ROOT record, exactly as `.jsonValues()` is: a
4955
+ * nested external record would leave a hole in the middle of a module's
4956
+ * source, and the Studio, patching and validation all assume a module's
4957
+ * source is complete apart from its entries.
4958
+ */
4959
+ }, {
4960
+ key: "external",
4961
+ value: function external(label) {
4962
+ if (this.isJsonValues) {
4963
+ throw new Error(".external() cannot be combined with .jsonValues(): entries live in one place or the other, not both.");
4964
+ }
4965
+ if (!label) {
4966
+ throw new Error('.external() requires a label, e.g. .external("posts")');
4967
+ }
4968
+ return new RecordSchema(this.item, this.opt,
4969
+ // Empty by construction: a validator declared before `.external()` is
4970
+ // typed against the un-external source shape and cannot be carried over.
4971
+ [], this.currentRouter, this.keySchema, this.mediaOptions, this.isReadonly, this.isHidden, this.description, this.isJsonValues, null, this.renderInput, label);
4972
+ }
4973
+
4974
+ /**
4975
+ * An external record's source is either the `c.external()` marker or entries
4976
+ * still written inline in the `.val.ts`.
4977
+ *
4978
+ * Inline entries are NOT a type error (see `ExternalRecordWritableSrc`), so
4979
+ * this is where they surface: as an `external:upload` fix, per key, the way
4980
+ * `.jsonValues()` reports an un-extracted entry. Reporting per key rather than
4981
+ * once for the record is what lets the fix be applied to one entry, and what
4982
+ * puts the error at a path the editor can navigate to.
4983
+ */
4984
+ }, {
4985
+ key: "validateExternal",
4986
+ value: function validateExternal(path, src) {
4987
+ if (this.opt && (src === null || src === undefined)) {
4988
+ return false;
4989
+ }
4990
+ if (src === null || _typeof(src) !== "object" || Array.isArray(src)) {
4991
+ return _defineProperty({}, path, [{
4992
+ message: "Expected the c.external() marker or inline entries, got '".concat(src === null ? "null" : Array.isArray(src) ? "array" : _typeof(src), "'"),
4993
+ value: src,
4994
+ schemaError: true
4995
+ }]);
4996
+ }
4997
+ if (isExternal(src)) {
4998
+ // The marker: the entries are the adapter's, and there is nothing here to
4999
+ // check. Their content is validated as it is read.
5000
+ return false;
5001
+ }
5002
+ var error = false;
5003
+ for (var _i = 0, _Object$keys = Object.keys(src); _i < _Object$keys.length; _i++) {
5004
+ var _key = _Object$keys[_i];
5005
+ var subPath = createValPathOfItem(path, _key);
5006
+ if (!subPath) {
5007
+ continue;
5008
+ }
5009
+ error = this.appendValidationError(error, subPath, "Entry is written inline, but this record is .external(\"".concat(this.externalLabel, "\"). Run 'val external upload' to move it into the store."), src[_key]);
5010
+ var errors = error;
5011
+ var last = errors[subPath][errors[subPath].length - 1];
5012
+ last.fixes = ["external:upload"];
5013
+ }
5014
+ return error;
4834
5015
  }
4835
5016
  }, {
4836
5017
  key: "getRouterValidations",
@@ -4884,7 +5065,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
4884
5065
  }, {
4885
5066
  key: "executeSerialize",
4886
5067
  value: function executeSerialize() {
4887
- var _this$renderInput, _this$keySchema, _this$currentRouter, _this$customValidateF;
5068
+ var _this$renderInput, _this$keySchema, _this$currentRouter, _this$customValidateF, _this$externalLabel;
4888
5069
  var result = {
4889
5070
  type: "record",
4890
5071
  render: (_this$renderInput = this.renderInput) !== null && _this$renderInput !== void 0 ? _this$renderInput : undefined,
@@ -4895,6 +5076,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
4895
5076
  router: (_this$currentRouter = this.currentRouter) === null || _this$currentRouter === void 0 ? void 0 : _this$currentRouter.getRouterId(),
4896
5077
  customValidate: this.customValidateFunctions && ((_this$customValidateF = this.customValidateFunctions) === null || _this$customValidateF === void 0 ? void 0 : _this$customValidateF.length) > 0,
4897
5078
  jsonValues: this.isJsonValues ? true : undefined,
5079
+ external: (_this$externalLabel = this.externalLabel) !== null && _this$externalLabel !== void 0 ? _this$externalLabel : undefined,
4898
5080
  readonly: this.isReadonly,
4899
5081
  hidden: this.isHidden,
4900
5082
  description: this.description
@@ -4932,8 +5114,12 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
4932
5114
  if (src === null) {
4933
5115
  return res;
4934
5116
  }
4935
- for (var _key in src) {
4936
- var itemSrc = src[_key];
5117
+ if (isExternal(src)) {
5118
+ // The entries are the adapter's; there is nothing here to preview.
5119
+ return res;
5120
+ }
5121
+ for (var _key2 in src) {
5122
+ var itemSrc = src[_key2];
4937
5123
  if (itemSrc === null || itemSrc === undefined) {
4938
5124
  continue;
4939
5125
  }
@@ -4944,14 +5130,14 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
4944
5130
  // keys, and the caller shows a placeholder for the rest.
4945
5131
  continue;
4946
5132
  }
4947
- var subPath = unsafeCreateSourcePath(sourcePath, _key);
5133
+ var subPath = unsafeCreateSourcePath(sourcePath, _key2);
4948
5134
  if (scope !== undefined && !scope.wantsUnder(subPath)) {
4949
5135
  continue;
4950
5136
  }
4951
5137
  var itemResult = this.item["executePreview"](subPath, itemSrc, scope);
4952
5138
  for (var keyS in itemResult) {
4953
- var _key2 = keyS;
4954
- res[_key2] = itemResult[_key2];
5139
+ var _key3 = keyS;
5140
+ res[_key3] = itemResult[_key3];
4955
5141
  }
4956
5142
  }
4957
5143
  // The entries preview comes from the ITEM schema's own `preview` — the
@@ -4962,9 +5148,9 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
4962
5148
  // is being shown, only the wanted entries when it is not.
4963
5149
  var window = scope !== undefined && !scope.wants(sourcePath) ? scope : null;
4964
5150
  var items = [];
4965
- for (var _i = 0, _Object$entries = Object.entries(src); _i < _Object$entries.length; _i++) {
4966
- var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
4967
- _key3 = _Object$entries$_i[0],
5151
+ for (var _i2 = 0, _Object$entries = Object.entries(src); _i2 < _Object$entries.length; _i2++) {
5152
+ var _Object$entries$_i = _slicedToArray(_Object$entries[_i2], 2),
5153
+ _key4 = _Object$entries$_i[0],
4968
5154
  val = _Object$entries$_i[1];
4969
5155
  if (isJson(val)) {
4970
5156
  continue; // as above: nothing to select from an un-loaded entry
@@ -4972,7 +5158,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
4972
5158
  if (val === null || val === undefined) {
4973
5159
  continue;
4974
5160
  }
4975
- if (window !== null && !window.wantsUnder(unsafeCreateSourcePath(sourcePath, _key3))) {
5161
+ if (window !== null && !window.wantsUnder(unsafeCreateSourcePath(sourcePath, _key4))) {
4976
5162
  continue;
4977
5163
  }
4978
5164
  // Per KEY, not per record: the closure is user code, and one entry whose
@@ -4984,14 +5170,14 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
4984
5170
  var title = _item.title,
4985
5171
  subtitle = _item.subtitle,
4986
5172
  image = _item.image;
4987
- items.push([_key3, {
5173
+ items.push([_key4, {
4988
5174
  title: title,
4989
5175
  subtitle: subtitle,
4990
5176
  image: image
4991
5177
  }]);
4992
5178
  }
4993
5179
  } catch (e) {
4994
- res[unsafeCreateSourcePath(sourcePath, _key3)] = {
5180
+ res[unsafeCreateSourcePath(sourcePath, _key4)] = {
4995
5181
  status: "error",
4996
5182
  message: e instanceof Error ? e.message : "Unknown error"
4997
5183
  };
@@ -5032,7 +5218,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5032
5218
  }, {
5033
5219
  key: "preview",
5034
5220
  value: function preview(select) {
5035
- return new RecordSchema(this.item, this.opt, this.customValidateFunctions, this.currentRouter, this.keySchema, this.mediaOptions, this.isReadonly, this.isHidden, this.description, this.isJsonValues, select, this.renderInput);
5221
+ return new RecordSchema(this.item, this.opt, this.customValidateFunctions, this.currentRouter, this.keySchema, this.mediaOptions, this.isReadonly, this.isHidden, this.description, this.isJsonValues, select, this.renderInput, this.externalLabel);
5036
5222
  }
5037
5223
 
5038
5224
  /**
@@ -5045,7 +5231,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
5045
5231
  }, {
5046
5232
  key: "render",
5047
5233
  value: function render(input) {
5048
- return new RecordSchema(this.item, this.opt, this.customValidateFunctions, this.currentRouter, this.keySchema, this.mediaOptions, this.isReadonly, this.isHidden, this.description, this.isJsonValues, this.previewInput, input);
5234
+ return new RecordSchema(this.item, this.opt, this.customValidateFunctions, this.currentRouter, this.keySchema, this.mediaOptions, this.isReadonly, this.isHidden, this.description, this.isJsonValues, this.previewInput, input, this.externalLabel);
5049
5235
  }
5050
5236
  }]);
5051
5237
  }(Schema);
@@ -5546,6 +5732,22 @@ function settings() {
5546
5732
  });
5547
5733
  }
5548
5734
 
5735
+ /**
5736
+ * What may be WRITTEN as an external record's source, on top of the marker.
5737
+ *
5738
+ * Entries inline in the `.val.ts` are accepted on purpose — the same call
5739
+ * `.jsonValues()` makes for the same reason: pasting content straight into the
5740
+ * module is the natural first thing to write, and a type error there is a dead
5741
+ * end, because the author cannot see what to write instead. Validation reports
5742
+ * them as an `external:upload` fix, and they are readable at runtime meanwhile.
5743
+ *
5744
+ * Widened HERE rather than in the schema's own source type so that the MODULE
5745
+ * stays typed on the marker alone. An adapter is bound to a module, and a module
5746
+ * whose type were "marker or plain record" would be indistinguishable from an
5747
+ * ordinary record — which would let `entry()` accept a module that is not
5748
+ * external at all.
5749
+ */
5750
+
5549
5751
  function define(id,
5550
5752
  // TODO: `/${string}`
5551
5753
  schema, source) {
@@ -6953,6 +7155,8 @@ function file(options) {
6953
7155
  /**
6954
7156
  * Define a collection of files.
6955
7157
  *
7158
+ * Remote is off by default: call `.remote()` on the result to allow remote files.
7159
+ *
6956
7160
  * @example
6957
7161
  * ```typescript
6958
7162
  * const schema = s.files({
@@ -6967,7 +7171,6 @@ function file(options) {
6967
7171
  * ```
6968
7172
  */
6969
7173
  var files = function files(options) {
6970
- var _options$remote;
6971
7174
  var directory = options.directory;
6972
7175
  var itemSchema = new ObjectSchema({
6973
7176
  mimeType: new StringSchema({}, false)
@@ -6976,7 +7179,7 @@ var files = function files(options) {
6976
7179
  type: "files",
6977
7180
  accept: options.accept,
6978
7181
  directory: directory,
6979
- remote: (_options$remote = options.remote) !== null && _options$remote !== void 0 ? _options$remote : false
7182
+ remote: false
6980
7183
  });
6981
7184
  };
6982
7185
 
@@ -8344,7 +8547,7 @@ function router(router, keyOrItem, maybeItem) {
8344
8547
  *
8345
8548
  * `directory` is required — it decides where uploads land, so it is not something
8346
8549
  * to be inferred. The rest defaults: any image type (`"image/*"`), nullable alt
8347
- * text, remote disabled.
8550
+ * text, remote disabled. Call `.remote()` on the result to allow remote images.
8348
8551
  *
8349
8552
  * @example
8350
8553
  * ```typescript
@@ -8364,7 +8567,7 @@ function router(router, keyOrItem, maybeItem) {
8364
8567
  * ```
8365
8568
  */
8366
8569
  var images = function images(options) {
8367
- var _options$alt, _options$accept, _options$remote;
8570
+ var _options$alt, _options$accept;
8368
8571
  var directory = options.directory;
8369
8572
  var altSchema = (_options$alt = options.alt) !== null && _options$alt !== void 0 ? _options$alt : string().nullable();
8370
8573
  var itemSchema = new ObjectSchema({
@@ -8377,7 +8580,7 @@ var images = function images(options) {
8377
8580
  type: "images",
8378
8581
  accept: (_options$accept = options.accept) !== null && _options$accept !== void 0 ? _options$accept : "image/*",
8379
8582
  directory: directory,
8380
- remote: (_options$remote = options.remote) !== null && _options$remote !== void 0 ? _options$remote : false,
8583
+ remote: false,
8381
8584
  altSchema: altSchema,
8382
8585
  encode: options.encode
8383
8586
  });
@@ -8455,7 +8658,8 @@ var initVal = function initVal(config) {
8455
8658
  },
8456
8659
  c: {
8457
8660
  define: define,
8458
- json: json
8661
+ json: json,
8662
+ external: external
8459
8663
  },
8460
8664
  s: s,
8461
8665
  config: config
@@ -9252,9 +9456,10 @@ function _extractValModules() {
9252
9456
  * The one answer to that question. There used to be two — `hasRemoteFileSchema`
9253
9457
  * in the server, gating whether `/save` demands remote credentials, and
9254
9458
  * `findRequiredRemoteFiles` in the Studio, gating the `/remote/settings` fetch —
9255
- * and they disagreed about `s.images({ remote: true })`: the Studio's counted a
9256
- * remote media record, the server's did not, because a media collection
9257
- * serializes as a `record` of metadata rather than as an image schema.
9459
+ * and they disagreed about `s.images({ ... }).remote()`: the Studio's version
9460
+ * counted a remote media record, the server's did not, because a media
9461
+ * collection serializes as a `record` of metadata rather than as an image
9462
+ * schema.
9258
9463
  *
9259
9464
  * The server's answer was the wrong one, and silently so. With it false,
9260
9465
  * `/save` runs `saveOrUploadFiles` in `skip-remote` mode, and that mode does not
@@ -9347,6 +9552,85 @@ function hasRemoteFileSchema(schema) {
9347
9552
  }
9348
9553
  }
9349
9554
 
9555
+ /**
9556
+ * Does this schema hold any media at all — remote or not?
9557
+ *
9558
+ * The sibling of {@link hasRemoteFileSchema}, and the same walk minus the
9559
+ * `remote` predicate. It answers the question an external record has to ask
9560
+ * before it will accept a binding: an item schema containing media needs a
9561
+ * `putFile`/`getFile` pair, and without one that is a schema error.
9562
+ *
9563
+ * It is a walk over the SERIALIZED schema for the reasons given above, and for
9564
+ * one more that is specific to this question: the two cases most likely to be
9565
+ * missed are invisible to a value type. A media collection (`s.images()` /
9566
+ * `s.files()`) serializes as a record of metadata with no image schema inside
9567
+ * it, and a richtext inline image lives in a constructor argument. A check
9568
+ * written against the item TYPE would silently pass both — and the gallery case
9569
+ * already has a data-loss incident attached to it (see above).
9570
+ */
9571
+ function hasMediaSchema(schema) {
9572
+ if (schema.type === "file" || schema.type === "image") {
9573
+ return true;
9574
+ } else if (schema.type === "richtext") {
9575
+ var _schema$options2;
9576
+ // `img` is `boolean | SerializedImageSchema`: `true` means inline images are
9577
+ // allowed with the DEFAULT image schema, and is just as much media as a
9578
+ // spelled-out one. `hasRemoteFileSchema` tests only for an object because a
9579
+ // default image is not remote — a different question with a different
9580
+ // answer, which is why these two walks are separate functions.
9581
+ if ((_schema$options2 = schema.options) !== null && _schema$options2 !== void 0 && _schema$options2.img) {
9582
+ return true;
9583
+ }
9584
+ return false;
9585
+ } else if (schema.type === "array") {
9586
+ return hasMediaSchema(schema.item);
9587
+ } else if (schema.type === "record") {
9588
+ // A media collection names its file by the record's KEY, so there is no
9589
+ // image schema inside to find: `mediaType` is where the answer is written.
9590
+ if (schema.mediaType) {
9591
+ return true;
9592
+ }
9593
+ return hasMediaSchema(schema.item);
9594
+ } else if (schema.type === "object") {
9595
+ for (var key in schema.items) {
9596
+ if (hasMediaSchema(schema.items[key])) {
9597
+ return true;
9598
+ }
9599
+ }
9600
+ return false;
9601
+ } else if (schema.type === "union") {
9602
+ var unionObjectSchema = typeof schema.key === "string" ? schema : undefined;
9603
+ if (unionObjectSchema) {
9604
+ var _iterator2 = _createForOfIteratorHelper(unionObjectSchema.items),
9605
+ _step2;
9606
+ try {
9607
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
9608
+ var item = _step2.value;
9609
+ if (hasMediaSchema(item)) {
9610
+ return true;
9611
+ }
9612
+ }
9613
+ } catch (err) {
9614
+ _iterator2.e(err);
9615
+ } finally {
9616
+ _iterator2.f();
9617
+ }
9618
+ }
9619
+ // A string union's items are literals, so there is nothing to look inside.
9620
+ return false;
9621
+ } else if (schema.type === "boolean" || schema.type === "number" || schema.type === "string" || schema.type === "literal" || schema.type === "date" || schema.type === "dateTime" || schema.type === "color" || schema.type === "code" || schema.type === "keyOf" || schema.type === "route" ||
9622
+ // A settings module holds the assistant's configuration, not content, and
9623
+ // nothing in it is media.
9624
+ schema.type === "settings") {
9625
+ return false;
9626
+ } else {
9627
+ // Same contract as above: adding a schema type without teaching this
9628
+ // function about it must not compile.
9629
+ var exhaustiveCheck = schema;
9630
+ throw new Error("Unexpected schema: ".concat(JSON.stringify(exhaustiveCheck)));
9631
+ }
9632
+ }
9633
+
9350
9634
  var DEFAULT_VAL_REMOTE_HOST = "https://remote.val.build";
9351
9635
 
9352
9636
  var PatchError = /*#__PURE__*/_createClass(function PatchError(message) {
@@ -9491,6 +9775,79 @@ function mediaPathOf(value) {
9491
9775
  return typeof path === "string" ? path : null;
9492
9776
  }
9493
9777
 
9778
+ /**
9779
+ * The reference stored for a file whose bytes live behind an external adapter.
9780
+ *
9781
+ * Deliberately the same shape of idea as a remote ref (`remote/splitRemoteRef`):
9782
+ * a URL-ish string that **ends with the file's virtual path**, so the file's
9783
+ * logical identity survives wherever its bytes happen to be.
9784
+ *
9785
+ * /api/val/external/documents/f/{fileHash}/p/public/val/documents/report_a1b2c.pdf
9786
+ * \__________________________/ \________/ \_______________________________/
9787
+ * route + label content the path a LOCAL file
9788
+ * address would have had
9789
+ *
9790
+ * Three things follow from that shape, and each is load-bearing:
9791
+ *
9792
+ * - **`media.ts` needs no change.** `isRemote` is `!path.startsWith("/public")`,
9793
+ * and this does not, so `Internal.mediaUrl` returns the ref as-is and Val's
9794
+ * route serves it. The media rule that has been got wrong repeatedly is left
9795
+ * exactly alone.
9796
+ * - **Moving between storage modes is mechanical.** External → local reads the
9797
+ * `public/…` suffix back out, fetches the bytes and writes them there; local →
9798
+ * external uploads the file already at that path. Nothing is renamed.
9799
+ * - **The filename is Val's, not the adapter's**, produced by the same
9800
+ * `createFilename` a local or remote file uses — so the same bytes get the
9801
+ * same name under all three storage modes.
9802
+ */
9803
+
9804
+ var RegEx = /^\/api\/val\/external\/([^/]+)\/f\/([^/]+)\/p\/(.+)$/;
9805
+ function createExternalFileRef(_ref) {
9806
+ var label = _ref.label,
9807
+ fileHash = _ref.fileHash,
9808
+ filePath = _ref.filePath;
9809
+ var withoutLeadingSlash = filePath.startsWith("/") ? filePath.slice(1) : filePath;
9810
+ if (!withoutLeadingSlash.startsWith("public/")) {
9811
+ throw new Error("External file paths must be under '/public', got: '".concat(filePath, "'"));
9812
+ }
9813
+ return "/api/val/external/".concat(label, "/f/").concat(fileHash, "/p/").concat(withoutLeadingSlash);
9814
+ }
9815
+ function splitExternalFileRef(ref) {
9816
+ var match = ref.match(RegEx);
9817
+ if (!match) {
9818
+ return {
9819
+ status: "error",
9820
+ error: "Not an external file ref: " + ref
9821
+ };
9822
+ }
9823
+ var filePath = match[3];
9824
+ if (!filePath.startsWith("public/")) {
9825
+ return {
9826
+ status: "error",
9827
+ error: "Invalid external file ref: " + ref
9828
+ };
9829
+ }
9830
+ // Traversal check, for the same reason `splitRemoteRef` has one: this path is
9831
+ // used to read and write files.
9832
+ if (filePath.split("/").some(function (segment) {
9833
+ return segment === "." || segment === "..";
9834
+ })) {
9835
+ return {
9836
+ status: "error",
9837
+ error: "Invalid external file ref: " + ref
9838
+ };
9839
+ }
9840
+ return {
9841
+ status: "success",
9842
+ label: match[1],
9843
+ fileHash: match[2],
9844
+ filePath: filePath
9845
+ };
9846
+ }
9847
+ function isExternalFileRef(ref) {
9848
+ return RegEx.test(ref);
9849
+ }
9850
+
9494
9851
  // NOTE: the segments must match the ref built by createRemoteRef below and the RegEx in splitRemoteRef.
9495
9852
 
9496
9853
  function createRemoteRef(remoteHost, _ref) {
@@ -9551,7 +9908,7 @@ function deserializeSchema(serialized) {
9551
9908
  return schema;
9552
9909
  }
9553
9910
  function deserializeSchemaImpl(serialized) {
9554
- var _serialized$options, _serialized$options2, _serialized$render, _serialized$multiline, _serialized$render2, _serialized$render3, _serialized$render4, _serialized$render5, _serialized$render6, _serialized$render7, _serialized$accept, _serialized$directory, _serialized$remote, _serialized$jsonValue, _serialized$render9, _serialized$render0, _serialized$render10, _serialized$render11, _serialized$render12, _serialized$render13, _serialized$render14, _serialized$render15;
9911
+ var _serialized$options, _serialized$options2, _serialized$render, _serialized$multiline, _serialized$render2, _serialized$render3, _serialized$render4, _serialized$render5, _serialized$render6, _serialized$render7, _serialized$accept, _serialized$directory, _serialized$remote, _serialized$jsonValue, _serialized$render9, _serialized$external, _serialized$render0, _serialized$render10, _serialized$render11, _serialized$render12, _serialized$render13, _serialized$render14, _serialized$render15;
9555
9912
  switch (serialized.type) {
9556
9913
  case "string":
9557
9914
  return new StringSchema(_objectSpread2(_objectSpread2({}, serialized.options), {}, {
@@ -9612,7 +9969,10 @@ function deserializeSchemaImpl(serialized) {
9612
9969
  altSchema: serialized.alt ? deserializeSchema(serialized.alt) : undefined
9613
9970
  } : undefined, false, false, serialized.description, (_serialized$jsonValue = serialized.jsonValues) !== null && _serialized$jsonValue !== void 0 ? _serialized$jsonValue : false,
9614
9971
  // The preview closure cannot survive serialization; the render can.
9615
- null, (_serialized$render9 = serialized.render) !== null && _serialized$render9 !== void 0 ? _serialized$render9 : null);
9972
+ null, (_serialized$render9 = serialized.render) !== null && _serialized$render9 !== void 0 ? _serialized$render9 : null, // The external label is static data, so it survives serialization and
9973
+ // must be carried back: a deserialized schema that forgot it would walk
9974
+ // an external record as if its entries were local.
9975
+ (_serialized$external = serialized.external) !== null && _serialized$external !== void 0 ? _serialized$external : null);
9616
9976
  case "keyOf":
9617
9977
  return new KeyOfSchema(serialized.schema, serialized.path, serialized.opt, [], false, false, serialized.description, (_serialized$render0 = serialized.render) !== null && _serialized$render0 !== void 0 ? _serialized$render0 : null);
9618
9978
  case "route":
@@ -10174,6 +10534,11 @@ var Internal = {
10174
10534
  isJson: isJson,
10175
10535
  getJsonImport: getJsonImport,
10176
10536
  resolveJsonValues: resolveJsonValues,
10537
+ isExternal: isExternal,
10538
+ hasInlineExternalEntries: hasInlineExternalEntries,
10539
+ createExternalFileRef: createExternalFileRef,
10540
+ splitExternalFileRef: splitExternalFileRef,
10541
+ isExternalFileRef: isExternalFileRef,
10177
10542
  createValPathOfItem: createValPathOfItem,
10178
10543
  getSHA256Hash: getSHA256Hash,
10179
10544
  initSchema: initSchema,
@@ -10238,4 +10603,4 @@ function tryJsonParse(str) {
10238
10603
  }
10239
10604
  }
10240
10605
 
10241
- export { ArraySchema as A, BooleanSchema as B, ColorSchema as C, DEFAULT_CONTENT_HOST as D, DEFAULT_COLOR_FORMAT as E, FATAL_ERROR_TYPES as F, GenericSelector as G, DateTimeSchema as H, Internal as I, RouteSchema as J, KeyOfSchema as K, RichTextSchema as L, ModuleFilePathSep as M, NumberSchema as N, ObjectSchema as O, PatchError as P, LiteralSchema as Q, RecordSchema as R, Schema as S, deserializeSchema as T, UnionSchema as U, VAL_EXTENSION as V, previewScope as W, isInlineRender as X, getSourcePathFromRoute as Y, _typeof as _, _slicedToArray as a, _createClass as b, _classCallCheck as c, _toConsumableArray as d, DEFAULT_APP_HOST as e, extractValModules as f, computeValModuleShas as g, hasRemoteFileSchema as h, initVal as i, DEFAULT_VAL_REMOTE_HOST as j, derefPatch as k, StringSchema as l, modules as m, ImageSchema as n, FileSchema as o, DateSchema as p, SettingsSchema as q, ASSISTANT_SETTINGS_MAX_LENGTH as r, splitModuleFilePathAndModulePath as s, assistantAvailability as t, SETTINGS_MODULE_CONVENTION as u, isRootModuleFilePath as v, resolveSettingsModule as w, CodeSchema as x, CODE_LANGUAGES as y, COLOR_FORMATS as z };
10606
+ export { ArraySchema as A, BooleanSchema as B, ColorSchema as C, DEFAULT_CONTENT_HOST as D, COLOR_FORMATS as E, FATAL_ERROR_TYPES as F, GenericSelector as G, DEFAULT_COLOR_FORMAT as H, Internal as I, DateTimeSchema as J, KeyOfSchema as K, RouteSchema as L, ModuleFilePathSep as M, NumberSchema as N, ObjectSchema as O, PatchError as P, RichTextSchema as Q, RecordSchema as R, Schema as S, LiteralSchema as T, UnionSchema as U, VAL_EXTENSION as V, deserializeSchema as W, previewScope as X, isInlineRender as Y, getSourcePathFromRoute as Z, _typeof as _, _slicedToArray as a, _createClass as b, _classCallCheck as c, _toConsumableArray as d, DEFAULT_APP_HOST as e, extractValModules as f, computeValModuleShas as g, hasRemoteFileSchema as h, initVal as i, hasMediaSchema as j, DEFAULT_VAL_REMOTE_HOST as k, derefPatch as l, modules as m, StringSchema as n, ImageSchema as o, FileSchema as p, DateSchema as q, SettingsSchema as r, splitModuleFilePathAndModulePath as s, ASSISTANT_SETTINGS_MAX_LENGTH as t, assistantAvailability as u, SETTINGS_MODULE_CONVENTION as v, isRootModuleFilePath as w, resolveSettingsModule as x, CodeSchema as y, CODE_LANGUAGES as z };