@valbuild/shared 0.122.0 → 0.123.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.
@@ -6,6 +6,11 @@ var core = require('@valbuild/core');
6
6
  var utils = require('../../dist/utils-d3d8b4b5.cjs.dev.js');
7
7
  var zod = require('zod');
8
8
  var zodValidationError = require('zod-validation-error');
9
+ var FlexSearch = require('flexsearch');
10
+
11
+ function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
12
+
13
+ var FlexSearch__default = /*#__PURE__*/_interopDefault(FlexSearch);
9
14
 
10
15
  function _toPrimitive(t, r) {
11
16
  if ("object" != typeof t || !t) return t;
@@ -4670,13 +4675,21 @@ function checkGallerySchema(moduleSchema) {
4670
4675
  };
4671
4676
  }
4672
4677
  function buildImageGalleryPatch(args, moduleSchema) {
4678
+ var _args$metadata$alt;
4673
4679
  var wrong = checkGallerySchema(moduleSchema);
4674
4680
  if (wrong) return wrong;
4675
4681
  var remote = isRemoteSchema(moduleSchema);
4676
4682
  var patch = [{
4677
4683
  op: "add",
4678
4684
  path: [args.filePath],
4679
- value: _objectSpread2({}, args.metadata)
4685
+ value: _objectSpread2(_objectSpread2({}, args.metadata), {}, {
4686
+ // `alt` is a REQUIRED field of a gallery entry (`s.images()` gives it a
4687
+ // nullable string schema), so an entry written without one is invalid
4688
+ // the moment it lands — and the caller that had no alt to give is the
4689
+ // common case, not the odd one. Null is what the Studio's own upload
4690
+ // writes there.
4691
+ alt: (_args$metadata$alt = args.metadata.alt) !== null && _args$metadata$alt !== void 0 ? _args$metadata$alt : null
4692
+ })
4680
4693
  }, {
4681
4694
  op: "file",
4682
4695
  path: [args.filePath],
@@ -5168,12 +5181,712 @@ function filterBlockingValidationErrors(validationErrors, schemas, sources) {
5168
5181
  return partitionValidationErrors(resolved).surfaced;
5169
5182
  }
5170
5183
 
5184
+ /**
5185
+ * Every decision `encode` makes, with none of the pixels.
5186
+ *
5187
+ * The re-encode itself happens in two places that share nothing: the Studio
5188
+ * runs it on a `<canvas>` before the upload leaves the browser
5189
+ * (`packages/ui/spa/utils/encodeImage.ts`), and the MCP image tool runs it on
5190
+ * `sharp` in the server process (`@valbuild/mcp`). What must NOT differ between
5191
+ * them is what `s.image({ encode })` means — which images are converted, how
5192
+ * far they are scaled, and when the original wins anyway. So the decisions live
5193
+ * here, once, and each side supplies only its own encoder.
5194
+ *
5195
+ * See `architecture/media.md`.
5196
+ */
5197
+
5198
+ var ENCODE_DEFAULTS = {
5199
+ quality: 0.8,
5200
+ maxWidth: 2560,
5201
+ maxHeight: 2560
5202
+ };
5203
+ var ENCODE_MIME_TYPE_OF = {
5204
+ webp: "image/webp"
5205
+ };
5206
+
5207
+ /**
5208
+ * Formats an encoder cannot round-trip without losing something: SVG is vector,
5209
+ * an animated GIF keeps only its first frame, and AVIF is already smaller than
5210
+ * anything we would produce.
5211
+ */
5212
+ var NEVER_ENCODED = ["image/svg+xml", "image/gif", "image/avif"];
5213
+
5214
+ /** A dimension bound the encoder can act on, else the default. */
5215
+ function usableBound(value, fallback) {
5216
+ return value !== undefined && Number.isFinite(value) && value > 0 ? value : fallback;
5217
+ }
5218
+
5219
+ /**
5220
+ * The schema's own option first, then the gallery it references, then off.
5221
+ *
5222
+ * Mirrors how `ImageField` resolves `accept` and `directory`. A gallery-backed
5223
+ * field (`s.image(galleryVal)`) serializes with EMPTY options, so without the
5224
+ * gallery fallback it could never inherit what the gallery asked for — and
5225
+ * `s.image(galleryVal, { encode: false })` is how such a field opts back out.
5226
+ *
5227
+ * Nonsense numbers fall back to the defaults rather than through. `fitWithin`
5228
+ * reads a bound of 0 (or NaN, or a negative) as "everything already fits", so
5229
+ * an unusable `maxWidth` would otherwise SILENTLY disable the downscale it was
5230
+ * asking for, and a `quality` outside 0–1 is ignored by `canvas.toBlob` just as
5231
+ * quietly. Falling back is deliberately not throwing: a schema is evaluated by
5232
+ * the CLI's `node:vm` sandbox and by the Next server, so a typo'd quality would
5233
+ * take a whole project down over an upload-time optimisation.
5234
+ */
5235
+ function resolveEncodeSettings(fieldOption, galleryOption) {
5236
+ var option = fieldOption !== undefined ? fieldOption : galleryOption;
5237
+ if (!option) {
5238
+ return null;
5239
+ }
5240
+ var quality = option.quality;
5241
+ return {
5242
+ type: option.type,
5243
+ quality: quality !== undefined && Number.isFinite(quality) && quality > 0 ? Math.min(quality, 1) : ENCODE_DEFAULTS.quality,
5244
+ maxWidth: usableBound(option.maxWidth, ENCODE_DEFAULTS.maxWidth),
5245
+ maxHeight: usableBound(option.maxHeight, ENCODE_DEFAULTS.maxHeight)
5246
+ };
5247
+ }
5248
+
5249
+ /**
5250
+ * Dimensions scaled to fit inside the box, or null when the image already does.
5251
+ *
5252
+ * Null is the signal for "no downscale was needed", which the size comparison
5253
+ * in {@link chooseEncoded} turns on.
5254
+ */
5255
+ function fitWithin(width, height, maxWidth, maxHeight) {
5256
+ if (!(width > 0) || !(height > 0) || !(maxWidth > 0) || !(maxHeight > 0) || width <= maxWidth && height <= maxHeight) {
5257
+ return null;
5258
+ }
5259
+ var scale = Math.min(maxWidth / width, maxHeight / height);
5260
+ return {
5261
+ // At least one pixel: a very wide, very short image would otherwise round
5262
+ // its height to zero, and a zero-sized canvas cannot be encoded.
5263
+ width: Math.max(1, Math.round(width * scale)),
5264
+ height: Math.max(1, Math.round(height * scale))
5265
+ };
5266
+ }
5267
+
5268
+ /**
5269
+ * Whether a schema that declares `accept` would take this mime type.
5270
+ *
5271
+ * The same rules as `ImageSchema.executeValidate`, which validates the STORED
5272
+ * mimeType against `accept` — so converting to a type `accept` forbids would
5273
+ * upload a file the schema reports as invalid the moment it lands. Delegated to
5274
+ * `@valbuild/core` rather than re-spelled, because a second reading of one
5275
+ * `accept` string is how a converted image passes here and fails there.
5276
+ */
5277
+ function isMimeTypeAccepted(mimeType, accept) {
5278
+ if (!accept) {
5279
+ return true;
5280
+ }
5281
+ return core.Internal.mimeTypeMatchesAccept(mimeType, accept);
5282
+ }
5283
+
5284
+ /** Formats left alone: the lossy list, plus the target type when it already fits. */
5285
+ function isSkippedSource(sourceMimeType, targetMimeType, needsDownscale) {
5286
+ if (NEVER_ENCODED.includes(sourceMimeType)) {
5287
+ return true;
5288
+ }
5289
+ return sourceMimeType === targetMimeType && !needsDownscale;
5290
+ }
5291
+
5292
+ /**
5293
+ * Whether to upload what the encoder produced, or the file that was picked.
5294
+ *
5295
+ * Two ways the encoded bytes lose. They are not actually the type we asked for
5296
+ * — `canvas.toBlob` answers an unsupported type with a PNG rather than with
5297
+ * null, so "not null" is not the same question as "is a webp". Or they are
5298
+ * simply bigger, which happens for small and flat images: measured, an 8x8
5299
+ * solid PNG of 74 bytes becomes a 548 byte webp. A downscale overrides that,
5300
+ * since the original is then the wrong size whatever it weighs.
5301
+ */
5302
+ function chooseEncoded(input) {
5303
+ if (input.encodedSize === null || input.encodedType !== input.targetMimeType) {
5304
+ return false;
5305
+ }
5306
+ if (input.needsDownscale) {
5307
+ return true;
5308
+ }
5309
+ return input.encodedSize < input.originalSize;
5310
+ }
5311
+
5312
+ /**
5313
+ * `photo.png` -> `photo.webp`. The extension is re-derived downstream too, but
5314
+ * the name should not lie.
5315
+ */
5316
+ function withExtension(filename, ext) {
5317
+ var base = filename.split(".").slice(0, -1).join(".") || filename;
5318
+ return "".concat(base, ".").concat(ext);
5319
+ }
5320
+
5321
+ /**
5322
+ * The one way to build a child's source path.
5323
+ *
5324
+ * There were five hand-rolled versions of this, three of them subtly different
5325
+ * and two of them wrong, and the wrong ones were wrong in a way that hid: a
5326
+ * module path segment has to be JSON-quoted if it is a string and bare if it is
5327
+ * an array index, and both broken copies appended the raw key at the module
5328
+ * root. An unquoted key parses fine as long as it contains no `.` — so every
5329
+ * object field and every ordinary record key still resolved, and only a key with
5330
+ * a dot in it broke. That is exactly what a media gallery is keyed by, so
5331
+ * clicking a file in the Media panel navigated to
5332
+ * `?p=/public/val/red-8x8_bfbd0.png`, which `splitModulePath` reads as the two
5333
+ * segments `/public/val/red-8x8_bfbd0` and `png`, and the studio said the module
5334
+ * was not found.
5335
+ *
5336
+ * So: one function, and never a template string. `Internal.createValPathOfItem`
5337
+ * is the rule itself — `JSON.stringify` quotes a string and leaves a number
5338
+ * bare, which is precisely the module-path grammar — and everything here defers
5339
+ * to it rather than restating it.
5340
+ */
5341
+ function sourcePathOfChild(parent, key) {
5342
+ /**
5343
+ * A parent that is already `…?p=` gets the key with no separator.
5344
+ *
5345
+ * The search index walks from `moduleFilePath + "?p="` rather than from the
5346
+ * module file path, so that every path it produces has a module path — and
5347
+ * `createValPathOfItem` would see the separator and join with a `.`, giving
5348
+ * `?p=."title"`.
5349
+ */
5350
+ if (parent.endsWith(core.Internal.ModuleFilePathSep)) {
5351
+ return "".concat(parent).concat(JSON.stringify(key));
5352
+ }
5353
+ var path = core.Internal.createValPathOfItem(parent, key);
5354
+ if (path === undefined) {
5355
+ // Only when the parent is empty, which callers cannot produce: every walk
5356
+ // starts from a module file path. Thrown rather than skipped so a future
5357
+ // change that breaks that assumption is loud.
5358
+ throw new Error("Could not build a source path for '".concat(key, "' under ").concat(parent));
5359
+ }
5360
+ return path;
5361
+ }
5362
+
5363
+ /**
5364
+ * The same thing, for callers holding the module file path and module path apart.
5365
+ *
5366
+ * `ModulePath` is empty at the module root, which is the case both broken copies
5367
+ * got wrong: there is no separator in the path yet, so the key is the first
5368
+ * segment and still has to be quoted.
5369
+ */
5370
+ function concatModulePath(moduleFilePath, modulePath, key) {
5371
+ return sourcePathOfChild(modulePath ? core.Internal.joinModuleFilePathAndModulePath(moduleFilePath, modulePath) : moduleFilePath, key);
5372
+ }
5373
+
5374
+ /**
5375
+ * Traverses a schema and source pair, calling a callback for each leaf node.
5376
+ * Handles all schema types including primitives, objects, arrays, records, unions, richtext, files, images, etc.
5377
+ *
5378
+ * @param source - The JSON source data
5379
+ * @param schema - The serialized schema
5380
+ * @param path - The current source path
5381
+ * @param callback - Function called for each traversable node with { source, schema, path }
5382
+ */
5383
+ function traverseSchemaSource(source, schema, path, callback) {
5384
+ if (!(schema !== null && schema !== void 0 && schema.type)) {
5385
+ throw new Error("Schema not found for " + path);
5386
+ }
5387
+ if (source === null) {
5388
+ return;
5389
+ }
5390
+
5391
+ // An un-loaded `.jsonValues()` entry is an opaque `{_type:"json"}` marker, not
5392
+ // the value its schema describes. Skip it — its content is traversed once
5393
+ // loaded, since the sync engine substitutes it in place of the marker.
5394
+ //
5395
+ // Without this the marker falls through to whichever branch its ITEM schema
5396
+ // selects: an object/record item walks the marker's OWN keys, so an entry
5397
+ // schema with a field named `_type` or `patch_id` would index the marker's
5398
+ // internals as if they were content.
5399
+ if (core.Internal.isJson(source)) {
5400
+ return;
5401
+ }
5402
+
5403
+ // Handle primitives
5404
+ if (schema.type === "string") {
5405
+ if (typeof source === "string") {
5406
+ callback({
5407
+ source: source,
5408
+ schema: schema,
5409
+ path: path
5410
+ });
5411
+ }
5412
+ return;
5413
+ }
5414
+ if (schema.type === "number") {
5415
+ if (typeof source === "number") {
5416
+ callback({
5417
+ source: source,
5418
+ schema: schema,
5419
+ path: path
5420
+ });
5421
+ }
5422
+ return;
5423
+ }
5424
+ if (schema.type === "boolean") {
5425
+ if (typeof source === "boolean") {
5426
+ callback({
5427
+ source: source,
5428
+ schema: schema,
5429
+ path: path
5430
+ });
5431
+ }
5432
+ return;
5433
+ }
5434
+
5435
+ // Handle richtext - flatten and call callback
5436
+ if (schema.type === "richtext") {
5437
+ callback({
5438
+ source: source,
5439
+ schema: schema,
5440
+ path: path
5441
+ });
5442
+ return;
5443
+ }
5444
+
5445
+ // Handle file/image
5446
+ if (schema.type === "file" || schema.type === "image") {
5447
+ if (source && _typeof(source) === "object" && "path" in source && typeof source.path === "string") {
5448
+ callback({
5449
+ source: source,
5450
+ schema: schema,
5451
+ path: path
5452
+ });
5453
+ }
5454
+ return;
5455
+ }
5456
+
5457
+ // Handle date
5458
+ if (schema.type === "date") {
5459
+ if (typeof source === "string") {
5460
+ callback({
5461
+ source: source,
5462
+ schema: schema,
5463
+ path: path
5464
+ });
5465
+ }
5466
+ return;
5467
+ }
5468
+
5469
+ // Handle dateTime
5470
+ if (schema.type === "dateTime") {
5471
+ if (typeof source === "string") {
5472
+ callback({
5473
+ source: source,
5474
+ schema: schema,
5475
+ path: path
5476
+ });
5477
+ }
5478
+ return;
5479
+ }
5480
+
5481
+ // Handle color
5482
+ if (schema.type === "color") {
5483
+ if (typeof source === "string") {
5484
+ callback({
5485
+ source: source,
5486
+ schema: schema,
5487
+ path: path
5488
+ });
5489
+ }
5490
+ return;
5491
+ }
5492
+
5493
+ // Handle code
5494
+ if (schema.type === "code") {
5495
+ if (typeof source === "string") {
5496
+ callback({
5497
+ source: source,
5498
+ schema: schema,
5499
+ path: path
5500
+ });
5501
+ }
5502
+ return;
5503
+ }
5504
+
5505
+ // Handle keyOf
5506
+ if (schema.type === "keyOf") {
5507
+ if (typeof source === "string" || typeof source === "number") {
5508
+ callback({
5509
+ source: source,
5510
+ schema: schema,
5511
+ path: path
5512
+ });
5513
+ }
5514
+ return;
5515
+ }
5516
+
5517
+ // Handle literal
5518
+ if (schema.type === "literal") {
5519
+ callback({
5520
+ source: source,
5521
+ schema: schema,
5522
+ path: path
5523
+ });
5524
+ return;
5525
+ }
5526
+
5527
+ // Handle route
5528
+ if (schema.type === "route") {
5529
+ if (typeof source === "string") {
5530
+ callback({
5531
+ source: source,
5532
+ schema: schema,
5533
+ path: path
5534
+ });
5535
+ }
5536
+ return;
5537
+ }
5538
+
5539
+ // Handle array
5540
+ if (schema.type === "array") {
5541
+ if (!Array.isArray(source)) {
5542
+ return;
5543
+ }
5544
+ if (!schema.item) {
5545
+ throw new Error("Schema (" + schema.type + ") item not found for " + path);
5546
+ }
5547
+ for (var i = 0; i < source.length; i++) {
5548
+ var subPath = sourcePathOfChild(path, i);
5549
+ traverseSchemaSource(source[i], schema.item, subPath, callback);
5550
+ }
5551
+ return;
5552
+ }
5553
+
5554
+ // Handle object, record and settings
5555
+ if (schema.type === "object" || schema.type === "record" ||
5556
+ // Settings walks like an object, and starting from the SOURCE is what makes
5557
+ // its optional keys work: an absent section has nothing to traverse.
5558
+ schema.type === "settings") {
5559
+ if (_typeof(source) !== "object" || source === null || Array.isArray(source)) {
5560
+ return;
5561
+ }
5562
+ for (var key in source) {
5563
+ var _schema$items;
5564
+ var subSchema = schema.type === "record" ? schema.item : (_schema$items = schema.items) === null || _schema$items === void 0 ? void 0 : _schema$items[key];
5565
+ if (!subSchema) {
5566
+ continue;
5567
+ }
5568
+ var _subPath = sourcePathOfChild(path, key);
5569
+ traverseSchemaSource(source[key], subSchema, _subPath, callback);
5570
+ }
5571
+ return;
5572
+ }
5573
+
5574
+ // Handle union
5575
+ if (schema.type === "union") {
5576
+ var schemaKey = schema.key;
5577
+ if (typeof schemaKey === "string") {
5578
+ // Tagged union - find matching sub-schema
5579
+ if (source && _typeof(source) === "object" && !Array.isArray(source) && schemaKey in source) {
5580
+ var itemKey = source[schemaKey];
5581
+ if (typeof itemKey === "string") {
5582
+ var schemaOfItem = schema.items.filter(function (item) {
5583
+ return item.type === "object";
5584
+ }).find(function (item) {
5585
+ var itemKeySchema = item.items[schemaKey];
5586
+ if ((itemKeySchema === null || itemKeySchema === void 0 ? void 0 : itemKeySchema.type) === "literal") {
5587
+ return itemKeySchema.value === itemKey;
5588
+ }
5589
+ return false;
5590
+ });
5591
+ if (schemaOfItem) {
5592
+ traverseSchemaSource(source, schemaOfItem, path, callback);
5593
+ }
5594
+ }
5595
+ }
5596
+ } else {
5597
+ // Literal union - treat as primitive
5598
+ if (typeof source === "string") {
5599
+ callback({
5600
+ source: source,
5601
+ schema: schema,
5602
+ path: path
5603
+ });
5604
+ }
5605
+ }
5606
+ return;
5607
+ }
5608
+
5609
+ // Exhaustive check
5610
+ var exhaustiveCheck = schema;
5611
+ throw new Error("Unsupported schema type: " + JSON.stringify(exhaustiveCheck));
5612
+ }
5613
+
5614
+ /**
5615
+ * Flattens richtext source to a plain string, extracting only text content.
5616
+ * Used for indexing richtext content in search.
5617
+ */
5618
+ function flattenRichText(source) {
5619
+ var res = "";
5620
+ function rec(child) {
5621
+ if (typeof child === "string") {
5622
+ res += child;
5623
+ } else {
5624
+ if (child && _typeof(child) === "object" && "children" in child && Array.isArray(child.children)) {
5625
+ var _iterator = _createForOfIteratorHelper(child.children),
5626
+ _step;
5627
+ try {
5628
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
5629
+ var c = _step.value;
5630
+ rec(c);
5631
+ }
5632
+ } catch (err) {
5633
+ _iterator.e(err);
5634
+ } finally {
5635
+ _iterator.f();
5636
+ }
5637
+ }
5638
+ }
5639
+ }
5640
+ rec({
5641
+ children: source
5642
+ });
5643
+ return res;
5644
+ }
5645
+
5646
+ /**
5647
+ * Resolves a file ref (local path or remote URL) to a clean local-style path.
5648
+ */
5649
+ function cleanRefPath(ref) {
5650
+ var remoteRefRes = core.Internal.remote.splitRemoteRef(ref);
5651
+ return remoteRefRes.status === "success" ? "/".concat(remoteRefRes.filePath) : ref;
5652
+ }
5653
+
5654
+ /**
5655
+ * Extract a human-readable filename from a file ref (local path or remote URL).
5656
+ * Handles both remote refs (via splitRemoteRef) and plain `/public/...` paths.
5657
+ */
5658
+ function getFilenameFromRef(ref) {
5659
+ var cleanPath = cleanRefPath(ref);
5660
+ return cleanPath.split("/").pop() || cleanPath;
5661
+ }
5662
+
5663
+ /**
5664
+ * Parse a file ref into its constituent parts: a clean path, filename, and
5665
+ * folder (the path relative to `/public`).
5666
+ *
5667
+ * The `folder` strips the standard `/public` prefix so it shows a
5668
+ * concise location like `/images` instead of `/public/images`.
5669
+ */
5670
+ function getRefParts(ref) {
5671
+ var cleanPath = cleanRefPath(ref);
5672
+ var filename = cleanPath.split("/").pop() || cleanPath;
5673
+ // Only strip a leading "/public" path segment: "/publicity/a.png" must keep its folder
5674
+ var folder = cleanPath.replace(/^\/public(?=\/|$)/, "").replace(/\/[^/]+$/, "") || "/";
5675
+ return {
5676
+ cleanPath: cleanPath,
5677
+ filename: filename,
5678
+ folder: folder
5679
+ };
5680
+ }
5681
+
5682
+ /**
5683
+ * The search index and the labels to render for its hits.
5684
+ *
5685
+ * Kept as a plain module rather than living inside a worker so it can be tested:
5686
+ * a worker entry runs `self.onmessage` on import, which no test environment here
5687
+ * provides.
5688
+ *
5689
+ * In `@valbuild/shared` rather than the Studio because there are now two
5690
+ * realms searching the same content: the Studio's `SearchStore`, which keeps an
5691
+ * index alive in a worker and re-indexes a module at a time, and the MCP
5692
+ * `search_content` tool, which builds one per call and throws it away. What
5693
+ * counts as a document, and what a hit is labelled, must not differ between
5694
+ * them — an agent and an editor searching the same project should find the same
5695
+ * things.
5696
+ */
5697
+
5698
+ /** An index with nothing in it. Fill it with {@link indexModule}. */
5699
+ function createSearchIndex() {
5700
+ return {
5701
+ index: new FlexSearch__default["default"].Index({
5702
+ tokenize: "forward"
5703
+ }),
5704
+ pathToLabel: new Map(),
5705
+ docsByModule: new Map()
5706
+ };
5707
+ }
5708
+
5709
+ /**
5710
+ * Index one module, replacing whatever was previously indexed for it.
5711
+ *
5712
+ * Remove-then-add rather than `update`, because the set of paths in a module is
5713
+ * not stable: deleting an array item or a record entry means documents that must
5714
+ * disappear, and `update` only revises ids it is given.
5715
+ *
5716
+ * `.jsonValues()` entries are indexed only once their content is loaded — an
5717
+ * un-loaded entry is an opaque marker that {@link traverseSchemaSource} skips, so
5718
+ * the index is naturally PARTIAL and grows as batches land. That is why the search
5719
+ * UI loads the entries on the first query and re-indexes (debounced) as they
5720
+ * arrive, rather than pretending an empty result set is an answer.
5721
+ */
5722
+ function indexModule(searchIndex, moduleFilePath, source, schema) {
5723
+ var index = searchIndex.index,
5724
+ pathToLabel = searchIndex.pathToLabel,
5725
+ docsByModule = searchIndex.docsByModule;
5726
+ removeModule(searchIndex, moduleFilePath);
5727
+ var docs = new Set();
5728
+ docsByModule.set(moduleFilePath, docs);
5729
+ var path = moduleFilePath + "?p=";
5730
+ traverseSchemaSource(source, schema, path, function (_ref) {
5731
+ var source = _ref.source,
5732
+ schema = _ref.schema,
5733
+ path = _ref.path;
5734
+ if (source === null) {
5735
+ return;
5736
+ }
5737
+ var searchText = "";
5738
+ var label = "";
5739
+
5740
+ // Handle primitives
5741
+ if (schema.type === "string" || schema.type === "number" || schema.type === "boolean" || schema.type === "date" || schema.type === "dateTime" || schema.type === "color" || schema.type === "code" || schema.type === "keyOf" || schema.type === "route") {
5742
+ var _source$toString, _source$toString2;
5743
+ searchText = (_source$toString = source === null || source === void 0 ? void 0 : source.toString()) !== null && _source$toString !== void 0 ? _source$toString : "";
5744
+ label = (_source$toString2 = source === null || source === void 0 ? void 0 : source.toString()) !== null && _source$toString2 !== void 0 ? _source$toString2 : "";
5745
+ } else if (schema.type === "literal") {
5746
+ searchText = schema.value.toString();
5747
+ label = schema.value.toString();
5748
+ }
5749
+ // Handle richtext - flatten to get text content
5750
+ else if (schema.type === "richtext") {
5751
+ searchText = flattenRichText(source);
5752
+ // Use first 50 chars as label
5753
+ label = searchText.substring(0, 50) || "richtext";
5754
+ }
5755
+ // Handle file/image
5756
+ else if (schema.type === "file" || schema.type === "image") {
5757
+ if (source !== null && _typeof(source) === "object" && "path" in source && typeof source.path === "string") {
5758
+ // The label is the bare filename - the folder is shown separately in
5759
+ // the UI - but both are searchable.
5760
+ var _getRefParts = getRefParts(source.path),
5761
+ filename = _getRefParts.filename,
5762
+ folder = _getRefParts.folder;
5763
+ var alt = "alt" in source ? source.alt : "";
5764
+ searchText = filename + " " + folder + " " + alt;
5765
+ label = filename;
5766
+ }
5767
+ }
5768
+
5769
+ // Add to index if we have search text
5770
+ if (searchText) {
5771
+ var _Internal$splitModule = core.Internal.splitModuleFilePathAndModulePath(path),
5772
+ _Internal$splitModule2 = _slicedToArray(_Internal$splitModule, 2),
5773
+ modulePath = _Internal$splitModule2[1];
5774
+ var cleanPath = fastRemoveNonWordChars(modulePath) + " ";
5775
+ index.add(path, cleanPath + " " + path + " " + searchText);
5776
+ pathToLabel.set(path, label);
5777
+ docs.add(path);
5778
+ }
5779
+ });
5780
+ }
5781
+
5782
+ /**
5783
+ * Drop everything indexed for one module.
5784
+ *
5785
+ * Also used for a module that has gone away entirely — otherwise its documents
5786
+ * stay searchable and a hit navigates to a path that no longer exists.
5787
+ */
5788
+ function removeModule(searchIndex, moduleFilePath) {
5789
+ var index = searchIndex.index,
5790
+ pathToLabel = searchIndex.pathToLabel,
5791
+ docsByModule = searchIndex.docsByModule;
5792
+ var existing = docsByModule.get(moduleFilePath);
5793
+ if (existing === undefined) return;
5794
+ var _iterator = _createForOfIteratorHelper(existing),
5795
+ _step;
5796
+ try {
5797
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
5798
+ var id = _step.value;
5799
+ index.remove(id);
5800
+ pathToLabel["delete"](id);
5801
+ }
5802
+ } catch (err) {
5803
+ _iterator.e(err);
5804
+ } finally {
5805
+ _iterator.f();
5806
+ }
5807
+ docsByModule["delete"](moduleFilePath);
5808
+ }
5809
+
5810
+ /**
5811
+ * Index every module, from scratch.
5812
+ *
5813
+ * Now a loop over {@link indexModule} rather than its own walk, so the full
5814
+ * build and the incremental one cannot drift in what they consider a document.
5815
+ */
5816
+ function buildSearchIndex(modules) {
5817
+ var searchIndex = createSearchIndex();
5818
+ for (var moduleFilePathS in modules) {
5819
+ var moduleFilePath = moduleFilePathS;
5820
+ var _modules$moduleFilePa = modules[moduleFilePath],
5821
+ source = _modules$moduleFilePa.source,
5822
+ schema = _modules$moduleFilePa.schema;
5823
+ indexModule(searchIndex, moduleFilePath, source, schema);
5824
+ }
5825
+ return searchIndex;
5826
+ }
5827
+ var NON_CHARS = new Set(["/", "-", "_", ".", ":", "?", "&", "=", "@", '"', "'"]);
5828
+ function fastRemoveNonWordChars(str) {
5829
+ var result = "";
5830
+ for (var i = 0; i < str.length; i++) {
5831
+ var _char = str[i];
5832
+ if (!NON_CHARS.has(_char)) {
5833
+ result += _char;
5834
+ } else {
5835
+ result += " ";
5836
+ }
5837
+ }
5838
+ return result;
5839
+ }
5840
+
5841
+ /**
5842
+ * How many hits are counted before `total` stops being a count.
5843
+ *
5844
+ * FlexSearch stops as soon as it has the number of ids it was asked for, so a
5845
+ * `total` taken from a page-sized search is just the page size again — which
5846
+ * reads as "that is all there is" to anyone who did not write it. Counting is
5847
+ * asking for more ids than the page needs, so it costs an array of ids and no
5848
+ * more; this bound is where that stops being free, and `totalIsLowerBound` says
5849
+ * it was reached rather than letting the number quietly lie.
5850
+ */
5851
+ var MAX_COUNTED_RESULTS = 10000;
5852
+ function performSearch(searchIndex, query) {
5853
+ var limit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 50;
5854
+ var offset = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
5855
+ if (searchIndex === null || !query.trim()) {
5856
+ return {
5857
+ results: [],
5858
+ total: 0,
5859
+ totalIsLowerBound: false
5860
+ };
5861
+ }
5862
+ var index = searchIndex.index,
5863
+ pathToLabel = searchIndex.pathToLabel;
5864
+ var counted = Math.max(offset + limit, MAX_COUNTED_RESULTS);
5865
+ var searchResults = index.search(query, {
5866
+ limit: counted
5867
+ });
5868
+ var total = searchResults.length;
5869
+ var paged = searchResults.slice(offset, offset + limit);
5870
+ return {
5871
+ results: paged.map(function (id) {
5872
+ return {
5873
+ path: id,
5874
+ label: pathToLabel.get(id) || id
5875
+ };
5876
+ }),
5877
+ total: total,
5878
+ totalIsLowerBound: total >= counted
5879
+ };
5880
+ }
5881
+
5171
5882
  exports.Api = Api;
5172
5883
  exports.BinaryFileRef = BinaryFileRef;
5173
5884
  exports.CodeOptions = CodeOptions;
5174
5885
  exports.ColorOptions = ColorOptions;
5175
5886
  exports.DEFAULT_ERROR_MESSAGE = DEFAULT_ERROR_MESSAGE;
5176
5887
  exports.DateOptions = DateOptions;
5888
+ exports.ENCODE_DEFAULTS = ENCODE_DEFAULTS;
5889
+ exports.ENCODE_MIME_TYPE_OF = ENCODE_MIME_TYPE_OF;
5177
5890
  exports.FileOptions = FileOptions;
5178
5891
  exports.HistoricalCommit = HistoricalCommit;
5179
5892
  exports.HistoricalModule = HistoricalModule;
@@ -5184,6 +5897,7 @@ exports.ImageOptions = ImageOptions;
5184
5897
  exports.JSONValue = JSONValue;
5185
5898
  exports.JSON_ENTRIES_BATCH_MAX = JSON_ENTRIES_BATCH_MAX;
5186
5899
  exports.ModuleComparison = ModuleComparison;
5900
+ exports.NEVER_ENCODED = NEVER_ENCODED;
5187
5901
  exports.ParentRef = ParentRef$1;
5188
5902
  exports.Patch = Patch;
5189
5903
  exports.PatchBlock = PatchBlock;
@@ -5238,8 +5952,12 @@ exports.buildEmptyAtPathPatch = buildEmptyAtPathPatch;
5238
5952
  exports.buildFileRefValue = buildFileRefValue;
5239
5953
  exports.buildImageGalleryPatch = buildImageGalleryPatch;
5240
5954
  exports.buildRemoveImageGalleryEntryPatch = buildRemoveImageGalleryEntryPatch;
5955
+ exports.buildSearchIndex = buildSearchIndex;
5241
5956
  exports.checkCompatibility = checkCompatibility;
5957
+ exports.chooseEncoded = chooseEncoded;
5958
+ exports.concatModulePath = concatModulePath;
5242
5959
  exports.createRegExpFromPattern = createRegExpFromPattern;
5960
+ exports.createSearchIndex = createSearchIndex;
5243
5961
  exports.createValClient = createValClient;
5244
5962
  exports.describeContainerAtPath = describeContainerAtPath;
5245
5963
  exports.emptyOf = emptyOf;
@@ -5247,15 +5965,22 @@ exports.explainIncompatible = explainIncompatible;
5247
5965
  exports.filterBlockingValidationErrors = filterBlockingValidationErrors;
5248
5966
  exports.filterRoutesByPatterns = filterRoutesByPatterns;
5249
5967
  exports.findSimilar = findSimilar;
5968
+ exports.fitWithin = fitWithin;
5969
+ exports.flattenRichText = flattenRichText;
5250
5970
  exports.getErrorMessageFromUnknownJson = getErrorMessageFromUnknownJson;
5971
+ exports.getFilenameFromRef = getFilenameFromRef;
5251
5972
  exports.getInlineImgInfo = getInlineImgInfo;
5252
5973
  exports.getNextAppRouterSitemapTree = getNextAppRouterSitemapTree;
5253
5974
  exports.getNextAppRouterSourceFolder = getNextAppRouterSourceFolder;
5254
5975
  exports.getPatternFromModuleFilePath = getPatternFromModuleFilePath;
5976
+ exports.getRefParts = getRefParts;
5255
5977
  exports.getRoutesOf = getRoutesOf;
5256
5978
  exports.getRoutesWithModulePaths = getRoutesWithModulePaths;
5257
5979
  exports.getSourceAt = getSourceAt;
5980
+ exports.indexModule = indexModule;
5981
+ exports.isMimeTypeAccepted = isMimeTypeAccepted;
5258
5982
  exports.isRemoteSchema = isRemoteSchema;
5983
+ exports.isSkippedSource = isSkippedSource;
5259
5984
  exports.isValCanvasFrame = isValCanvasFrame;
5260
5985
  exports.isValCanvasPageMessage = isValCanvasPageMessage;
5261
5986
  exports.isValCanvasStudioMessage = isValCanvasStudioMessage;
@@ -5263,13 +5988,19 @@ exports.levenshtein = levenshtein;
5263
5988
  exports.newestCommitSha = newestCommitSha;
5264
5989
  exports.parseRoutePattern = parseRoutePattern;
5265
5990
  exports.partitionValidationErrors = partitionValidationErrors;
5991
+ exports.performSearch = performSearch;
5266
5992
  exports.remirrorToRichTextSource = remirrorToRichTextSource;
5993
+ exports.removeModule = removeModule;
5994
+ exports.resolveEncodeSettings = resolveEncodeSettings;
5267
5995
  exports.resolveSchemaSourceFixForError = resolveSchemaSourceFixForError;
5268
5996
  exports.resolveSchemaSourceFixes = resolveSchemaSourceFixes;
5269
5997
  exports.resolveSerializedSchemaAtPath = resolveSerializedSchemaAtPath;
5270
5998
  exports.richTextToRemirror = richTextToRemirror;
5271
5999
  exports.safeParsePatch = safeParsePatch;
6000
+ exports.sourcePathOfChild = sourcePathOfChild;
5272
6001
  exports.toolNames = toolNames;
6002
+ exports.traverseSchemaSource = traverseSchemaSource;
5273
6003
  exports.urlOf = urlOf;
5274
6004
  exports.validateRoutePatterns = validateRoutePatterns;
6005
+ exports.withExtension = withExtension;
5275
6006
  exports.withValCanvasParam = withValCanvasParam;