@valbuild/shared 0.150.18 → 0.151.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 (35) hide show
  1. package/CHANGELOG.md +246 -0
  2. package/client/dist/valbuild-shared-client.cjs.dev.js +2 -1
  3. package/client/dist/valbuild-shared-client.cjs.prod.js +2 -1
  4. package/client/dist/valbuild-shared-client.esm.js +1 -1
  5. package/dist/declarations/src/internal/ApiRoutes.d.ts +253 -2
  6. package/dist/declarations/src/internal/content/applyChain.d.ts +124 -0
  7. package/dist/declarations/src/internal/content/buildRegistry.d.ts +36 -0
  8. package/dist/declarations/src/internal/content/jsonValuesOps.d.ts +110 -0
  9. package/dist/declarations/src/internal/content/moduleStore.d.ts +119 -0
  10. package/dist/declarations/src/internal/content/source.d.ts +71 -0
  11. package/dist/declarations/src/internal/content/version.d.ts +151 -0
  12. package/dist/declarations/src/internal/index.d.ts +8 -1
  13. package/dist/declarations/src/internal/publish/projectSource.d.ts +28 -0
  14. package/dist/declarations/src/internal/quickPreview.d.ts +17 -1
  15. package/dist/declarations/src/internal/search/searchIndex.d.ts +34 -3
  16. package/dist/declarations/src/internal/server/types.d.ts +9 -1
  17. package/dist/declarations/src/internal/valCanvasProtocol.d.ts +46 -0
  18. package/dist/declarations/src/internal/valClientHeader.d.ts +36 -0
  19. package/dist/declarations/src/publish/callJson.d.ts +15 -0
  20. package/dist/declarations/src/publish/jobClient.d.ts +7 -1
  21. package/dist/declarations/src/publish/publishClient.d.ts +2 -14
  22. package/dist/{getSitemapTree-7c848c94.esm.js → getSitemapTree-3c1b9e57.esm.js} +37 -4
  23. package/dist/{getSitemapTree-98642b57.cjs.dev.js → getSitemapTree-59c5ed5d.cjs.dev.js} +37 -3
  24. package/dist/{getSitemapTree-97713490.cjs.prod.js → getSitemapTree-6b9024d1.cjs.prod.js} +37 -3
  25. package/dist/{protocol-d0efb8cd.cjs.dev.js → projectSource-2a58ef15.cjs.dev.js} +221 -0
  26. package/dist/{protocol-55cfa3cf.esm.js → projectSource-7f0ee83c.esm.js} +216 -1
  27. package/dist/{protocol-b1055ef0.cjs.prod.js → projectSource-ed6aec39.cjs.prod.js} +221 -0
  28. package/internal/dist/valbuild-shared-internal.cjs.dev.js +2388 -189
  29. package/internal/dist/valbuild-shared-internal.cjs.prod.js +2388 -189
  30. package/internal/dist/valbuild-shared-internal.esm.js +2332 -172
  31. package/package.json +3 -3
  32. package/publish/dist/valbuild-shared-publish.cjs.dev.js +48 -155
  33. package/publish/dist/valbuild-shared-publish.cjs.prod.js +48 -155
  34. package/publish/dist/valbuild-shared-publish.esm.js +17 -123
  35. package/dist/declarations/src/internal/server/requestScopedMemo.d.ts +0 -49
@@ -1,12 +1,14 @@
1
1
  import { f as _createForOfIteratorHelper, d as _objectSpread2, e as _toConsumableArray, _ as _typeof, g as _defineProperty, b as _asyncToGenerator, c as _regenerator, a as _slicedToArray } from '../../dist/createForOfIteratorHelper-9b80f1a2.esm.js';
2
- import { Internal, CODE_LANGUAGES, resolveSettingsModule, missingDeclaredKeys, localeOfValue, routePatternOf, matchRoutePattern, discriminatedUnionBranchOf, DEFAULT_COLOR_FORMAT, declaredKeySetOf } from '@valbuild/core';
2
+ import { Internal, CODE_LANGUAGES, VAL_EXTENSION, resolveSettingsModule, missingDeclaredKeys, localeOfValue, routePatternOf, matchRoutePattern, discriminatedUnionBranchOf, DEFAULT_COLOR_FORMAT, declaredKeySetOf } from '@valbuild/core';
3
3
  import { z } from 'zod';
4
- import { _ as _objectWithoutProperties } from '../../dist/protocol-55cfa3cf.esm.js';
5
- export { P as PublishProtocolError, b as parseArtifacts, p as parseBuildTarget, l as parseCancel, a as parseDeclare, m as parseDiscard, j as parseJob, n as parseNewestCiRun, h as parsePress, g as parseProblems, f as parseProjectSource, d as parsePromote, o as parsePublishToken, k as parseRenew, i as parseRequestStatus, e as parseStatus, q as parseUpdateTarget, c as parseVerify } from '../../dist/protocol-55cfa3cf.esm.js';
4
+ import { _ as _objectWithoutProperties } from '../../dist/projectSource-7f0ee83c.esm.js';
5
+ export { P as PublishProtocolError, v as createProjectSourceReader, c as parseArtifacts, a as parseBuildTarget, n as parseCancel, b as parseDeclare, o as parseDiscard, l as parseJob, q as parseNewestCiRun, j as parsePress, i as parseProblems, h as parseProjectSource, e as parsePromote, t as parsePublishToken, m as parseRenew, k as parseRequestStatus, f as parseStatus, u as parseUpdateTarget, d as parseVerify, p as publishApiOf } from '../../dist/projectSource-7f0ee83c.esm.js';
6
+ import { a as _createClass, c as _classCallCheck } from '../../dist/wrapNativeSuper-639b12b7.esm.js';
6
7
  import { fromError } from 'zod-validation-error';
7
- export { D as DEFAULT_CANVAS_SELECTION, e as DEFAULT_CANVAS_SELECTION_SOFT, Q as QUICK_PREVIEW_PARAM, u as QUICK_PREVIEW_SERVICE_WORKER_PATH, y as QUICK_PREVIEW_SERVICE_WORKER_SOURCE, v as QUICK_PREVIEW_STORE_MESSAGE, b as VAL_AI_SESSION_STORAGE_KEY, d as VAL_CANVAS_MESSAGE, c as VAL_CANVAS_PARAM, V as VAL_CONFIG_SESSION_STORAGE_KEY, a as VAL_THEME_SESSION_STORAGE_KEY, j as attachElementPicker, k as createPickerOverlay, h as describeElement, l as elementLabel, q as findBySelector, F as formatRoutePattern, G as getNextAppRouterSourceFolder, K as getPageRouterSitemapTree, I as getPageRouterSourceFolder, L as getPatternFromModuleFilePath, H as getTanStackRouterSourceFolder, C as isDesignTabOrigin, x as isQuickPreviewFiles, J as isTanStackRoutesFolder, g as isValCanvasFrame, i as isValCanvasPageMessage, f as isValCanvasStudioMessage, m as modifierLabel, p as pageLocationOf, E as parseRoutePattern, B as quickPreviewSiteOrigin, o as readPageSnapshot, r as restoreScroll, t as screenshotPage, s as startElementPicking, z as storeQuickPreview, n as watchPageLocation, A as withQuickPreviewParam, w as withValCanvasParam } from '../../dist/getSitemapTree-7c848c94.esm.js';
8
+ import { result, array } from '@valbuild/core/fp';
9
+ import { PatchError, deepEqual, parseAndValidateArrayIndex } from '@valbuild/core/patch';
10
+ export { D as DEFAULT_CANVAS_SELECTION, e as DEFAULT_CANVAS_SELECTION_SOFT, Q as QUICK_PREVIEW_PARAM, v as QUICK_PREVIEW_RENDER_SCRIPT, u as QUICK_PREVIEW_SERVICE_WORKER_PATH, z as QUICK_PREVIEW_SERVICE_WORKER_SOURCE, x as QUICK_PREVIEW_STORE_MESSAGE, b as VAL_AI_SESSION_STORAGE_KEY, d as VAL_CANVAS_MESSAGE, c as VAL_CANVAS_PARAM, V as VAL_CONFIG_SESSION_STORAGE_KEY, a as VAL_THEME_SESSION_STORAGE_KEY, j as attachElementPicker, k as createPickerOverlay, h as describeElement, l as elementLabel, q as findBySelector, G as formatRoutePattern, H as getNextAppRouterSourceFolder, L as getPageRouterSitemapTree, J as getPageRouterSourceFolder, M as getPatternFromModuleFilePath, I as getTanStackRouterSourceFolder, E as isDesignTabOrigin, y as isQuickPreviewFiles, K as isTanStackRoutesFolder, g as isValCanvasFrame, i as isValCanvasPageMessage, f as isValCanvasStudioMessage, m as modifierLabel, p as pageLocationOf, F as parseRoutePattern, C as quickPreviewSiteOrigin, o as readPageSnapshot, r as restoreScroll, t as screenshotPage, s as startElementPicking, A as storeQuickPreview, n as watchPageLocation, B as withQuickPreviewParam, w as withValCanvasParam } from '../../dist/getSitemapTree-3c1b9e57.esm.js';
8
11
  import FlexSearch from 'flexsearch';
9
- import { a as _createClass, c as _classCallCheck } from '../../dist/wrapNativeSuper-639b12b7.esm.js';
10
12
  import { b as base64DataUrlToUint8Array } from '../../dist/utils-00f3e1df.esm.js';
11
13
 
12
14
  function remirrorToRichTextSource(node, configDirectory, remoteOptions) {
@@ -197,7 +199,7 @@ var convertToNumber = function convertToNumber(value) {
197
199
  var parsedValue = parseInt(value, 10);
198
200
  return isNaN(parsedValue) ? 0 : parsedValue;
199
201
  };
200
- var textEncoder = new TextEncoder();
202
+ var textEncoder$1 = new TextEncoder();
201
203
  function convertImageNode(path, node, files, configDirectory, remoteOptions) {
202
204
  if (node.attrs && node.attrs.src.startsWith("data:")) {
203
205
  var binaryData = base64DataUrlToUint8Array(node.attrs.src);
@@ -223,7 +225,7 @@ function convertImageNode(path, node, files, configDirectory, remoteOptions) {
223
225
  validationHash: Internal.remote.getValidationHash(remoteOptions.coreVersion, remoteOptions.schema, mimeType, {
224
226
  width: typeof node.attrs.width === "number" ? node.attrs.width : 0,
225
227
  height: typeof node.attrs.height === "number" ? node.attrs.height : 0
226
- }, remoteFileHash, textEncoder),
228
+ }, remoteFileHash, textEncoder$1),
227
229
  filePath: filePath.slice(1)
228
230
  })) : filePath;
229
231
  if (existingFilesEntry) {
@@ -741,62 +743,9 @@ var VAL_STATE_COOKIE = Internal.VAL_STATE_COOKIE;
741
743
  var VAL_ENABLE_COOKIE_NAME = Internal.VAL_ENABLE_COOKIE_NAME;
742
744
 
743
745
  /**
744
- * One value, computed once per REQUEST.
745
- *
746
- * `fetchVal` in draft mode reads the whole module tree out of `/sources/~`, and
747
- * a page that calls it three times used to ask three times — the answer being
748
- * byte-identical each time, because the query and the session are fixed for the
749
- * length of a request. This is what makes the second and third call free.
750
- *
751
- * ## Why the scope is the REQUEST
752
- *
753
- * The memoised response is a DRAFT -- unpublished work, read with the caller's
754
- * session cookie. Every caller allowed to read drafts gets the same draft (patch
755
- * groups, which made it per author, are gone), but whether a caller is allowed
756
- * at all is decided per session. A memo that outlived the request would hand
757
- * the draft to the next visitor without asking, and it would be invisible: the
758
- * page would simply render somebody's half-finished sentence. Sharing it across
759
- * requests is possible now, keyed on the chain's head, and would be its own
760
- * measured change.
761
- *
762
- * So this module holds NO state of its own. The caller supplies the box, and
763
- * the box has to come from something the framework already scopes to the
764
- * request — React's `cache()` in an RSC, a `WeakMap` keyed on the `Request` in
765
- * TanStack Start. `null` means "no request scope available", and that is not an
766
- * error: it falls back to computing every time, which is what the code did
767
- * before this existed. Ineffective is a safe failure here; leaking is not.
768
- *
769
- * The `key` is the second half of that guarantee. It carries whatever the
770
- * answer depends on — the session — so a box that somehow outlived its request,
771
- * or is shared by two sessions, MISSES rather than serving the wrong draft.
772
- *
773
- * ## The promise is cached, not the value
774
- *
775
- * React renders siblings concurrently, so three `fetchVal` calls in one page
776
- * are usually in flight at the same time rather than one after another.
777
- * Caching the resolved value would dedupe none of them; caching the promise
778
- * dedupes both orderings.
779
- */
780
-
781
- /**
782
- * Returns the memoised promise for `key`, computing it only on a miss.
783
- *
784
- * A rejection is memoised along with everything else, on purpose: one failed
785
- * read per request is the same answer for every caller in it, and retrying it
786
- * two more times only makes a broken draft slower to render.
746
+ * Nothing moved since the ETag the caller sent back: no body, as HTTP
747
+ * requires of a 304.
787
748
  */
788
- function memoizePerRequest(box, key, compute) {
789
- if (box === null) {
790
- return compute();
791
- }
792
- if (box.promise !== undefined && box.key === key) {
793
- return box.promise;
794
- }
795
- var promise = compute();
796
- box.key = key;
797
- box.promise = promise;
798
- return promise;
799
- }
800
749
 
801
750
  /**
802
751
  * JSON, recursively — the shape of a Source over the wire.
@@ -1302,6 +1251,228 @@ var ValDeployment = z.object({
1302
1251
  commitMessage: z.string().nullable().optional()
1303
1252
  });
1304
1253
 
1254
+ var _Internal$VERSION$cor;
1255
+
1256
+ /**
1257
+ * What a module's content IS: the schema it is read and patched with, the
1258
+ * base it starts from, and the chain of patches on top. Three parts because
1259
+ * each kind of change moves exactly one of them (see
1260
+ * `docs/plans/one-content-model.md`, "A version, per module, with the code in
1261
+ * it"):
1262
+ *
1263
+ * - a schema change, or a new engine (`@valbuild/*` upgraded), moves `schema`;
1264
+ * - a publish, a content version, a literal edited in code, moves `base`;
1265
+ * - an edit, a discard, a publish (which empties the chain) moves `chain`.
1266
+ *
1267
+ * Two answers with the same version are the same content, wherever they came
1268
+ * from. That is the whole point: comparing contents becomes comparing three
1269
+ * strings.
1270
+ *
1271
+ * Computed when content CHANGES -- a module evaluated, a base adopted, a patch
1272
+ * appended -- never when it is read (risk 3 in the plan): every function here
1273
+ * that hashes counts itself ({@link versionHashCount}), so a reader that
1274
+ * hashes is caught by a counted budget rather than by a profile.
1275
+ */
1276
+
1277
+ /**
1278
+ * What a project answer is relative to: which build served it, and how far
1279
+ * along the project's changes it is.
1280
+ */
1281
+
1282
+ /**
1283
+ * What the schema version of a module staged after the build serving a
1284
+ * proposal starts with: `staged:<inputs>`. Val's server has not got that
1285
+ * module's schema, so it names the staging instead; a Studio compares no
1286
+ * build's code against it.
1287
+ */
1288
+ var STAGED_SCHEMA_PREFIX = "staged:";
1289
+
1290
+ /** The chain of no patches. */
1291
+ var EMPTY_CHAIN = "";
1292
+
1293
+ /**
1294
+ * The engine: how patches are applied and content is read. Part of every
1295
+ * schema version, because content read by another engine is not the same
1296
+ * content even under the same schema.
1297
+ */
1298
+ var ENGINE = "@valbuild/core@".concat((_Internal$VERSION$cor = Internal.VERSION.core) !== null && _Internal$VERSION$cor !== void 0 ? _Internal$VERSION$cor : "unknown");
1299
+ var hashes = 0;
1300
+ var textEncoder;
1301
+ function sha256Hex(input) {
1302
+ hashes++;
1303
+ textEncoder !== null && textEncoder !== void 0 ? textEncoder : textEncoder = new TextEncoder();
1304
+ return Internal.getSHA256Hash(textEncoder.encode(input));
1305
+ }
1306
+
1307
+ /**
1308
+ * How many times a version has been hashed in this process: the counted
1309
+ * budget behind "versions are computed when content changes, never when it
1310
+ * is read" (`version:hash`). A test takes it before and after a read.
1311
+ */
1312
+ function versionHashCount() {
1313
+ return hashes;
1314
+ }
1315
+
1316
+ /** A chain one patch longer. */
1317
+ function extendChain(prev, id) {
1318
+ return sha256Hex("".concat(prev, "\n").concat(id));
1319
+ }
1320
+
1321
+ /** The chain of these patch ids, in order, on top of a base. */
1322
+ function chainOf(ids) {
1323
+ var chain = EMPTY_CHAIN;
1324
+ var _iterator = _createForOfIteratorHelper(ids),
1325
+ _step;
1326
+ try {
1327
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
1328
+ var id = _step.value;
1329
+ chain = extendChain(chain, id);
1330
+ }
1331
+ } catch (err) {
1332
+ _iterator.e(err);
1333
+ } finally {
1334
+ _iterator.f();
1335
+ }
1336
+ return chain;
1337
+ }
1338
+
1339
+ /**
1340
+ * A module's schema version, from its serialized schema. The same function
1341
+ * on the server (from what `extractValModules` serialized) and in the Studio
1342
+ * (from what it holds in `SchemaStore`), so the two compare.
1343
+ */
1344
+ function schemaVersionOf(serializedSchema) {
1345
+ return sha256Hex("".concat(ENGINE, "\n").concat(JSON.stringify(serializedSchema)));
1346
+ }
1347
+
1348
+ /**
1349
+ * A module's base version, from its base source: what the module evaluates
1350
+ * to, or what a content version or a publish put in its place. `.jsonValues()`
1351
+ * entries are markers here; an entry's own version comes with entries stored
1352
+ * as modules of their own (phase 6).
1353
+ */
1354
+ function baseVersionOf(source) {
1355
+ var _JSON$stringify;
1356
+ return sha256Hex((_JSON$stringify = JSON.stringify(source)) !== null && _JSON$stringify !== void 0 ? _JSON$stringify : "undefined");
1357
+ }
1358
+
1359
+ /** The address of a module's content. Never changes meaning. */
1360
+ function moduleKey(path, v) {
1361
+ return "".concat(path, "@").concat(v.schema, ".").concat(v.base, ".").concat(v.chain);
1362
+ }
1363
+
1364
+ /** Whether two versions name the same content. */
1365
+ function sameModuleVersion(a, b) {
1366
+ return a.schema === b.schema && a.base === b.base && a.chain === b.chain;
1367
+ }
1368
+
1369
+ /**
1370
+ * The chains of every module, from the patches a draft applies (in chain
1371
+ * order), folded incrementally.
1372
+ *
1373
+ * A read asks for the chains every time; the fold is extended only by the
1374
+ * patches the chain has gained since the last call, and a call that finds the
1375
+ * chain as it was hashes nothing. A chain that changed anywhere but at its end
1376
+ * (a discard in the middle, a publish) is folded again from the start --
1377
+ * once, when that is first noticed.
1378
+ */
1379
+ var ChainFold = /*#__PURE__*/function () {
1380
+ function ChainFold() {
1381
+ _classCallCheck(this, ChainFold);
1382
+ _defineProperty(this, "ids", []);
1383
+ _defineProperty(this, "paths", []);
1384
+ _defineProperty(this, "chains", new Map());
1385
+ /**
1386
+ * Every module's chain after each of its patches, in order: what
1387
+ * {@link chainAt} answers for a module whose content stopped part way
1388
+ * along its chain (a patch that does not apply). Kept as the chains are
1389
+ * extended, so asking hashes nothing.
1390
+ */
1391
+ _defineProperty(this, "prefixes", new Map());
1392
+ /**
1393
+ * Bumped whenever the chains move: a cheap identity for "the same chains as
1394
+ * before", in-process. With {@link instance} in front, two folds never name
1395
+ * one epoch: two processes (or isolates) holding the same chains answer
1396
+ * differently, which only costs a cache a miss, where two holding
1397
+ * DIFFERENT chains at the same count must never answer alike (an ETag
1398
+ * answered 304 for content the caller does not have).
1399
+ */
1400
+ _defineProperty(this, "epochValue", 0);
1401
+ _defineProperty(this, "instance", Math.random().toString(36).slice(2, 10));
1402
+ }
1403
+ return _createClass(ChainFold, [{
1404
+ key: "chainsOf",
1405
+ value: /** Every module's chain, for these patches. Modules with none are absent. */
1406
+ function chainsOf(patches) {
1407
+ var known = this.ids.length;
1408
+ var extends_ = patches.length >= known;
1409
+ for (var i = 0; extends_ && i < known; i++) {
1410
+ var patch = patches[i];
1411
+ extends_ = patch !== undefined && patch.patchId === this.ids[i] && patch.path === this.paths[i];
1412
+ }
1413
+ if (extends_ && patches.length === known) {
1414
+ return this.chains;
1415
+ }
1416
+ var chains = extends_ ? new Map(this.chains) : new Map();
1417
+ var from = extends_ ? known : 0;
1418
+ if (!extends_) {
1419
+ this.ids = [];
1420
+ this.paths = [];
1421
+ this.prefixes = new Map();
1422
+ }
1423
+ for (var _i = from; _i < patches.length; _i++) {
1424
+ var _chains$get;
1425
+ var _patch = patches[_i];
1426
+ if (_patch === undefined) continue;
1427
+ var chain = extendChain((_chains$get = chains.get(_patch.path)) !== null && _chains$get !== void 0 ? _chains$get : EMPTY_CHAIN, _patch.patchId);
1428
+ chains.set(_patch.path, chain);
1429
+ var prefixes = this.prefixes.get(_patch.path);
1430
+ if (prefixes === undefined) this.prefixes.set(_patch.path, [chain]);else prefixes.push(chain);
1431
+ this.ids.push(_patch.patchId);
1432
+ this.paths.push(_patch.path);
1433
+ }
1434
+ this.chains = chains;
1435
+ this.epochValue++;
1436
+ return chains;
1437
+ }
1438
+
1439
+ /**
1440
+ * A module's chain after its first `count` patches, of those the last
1441
+ * {@link chainsOf} was given: the version of content that stopped at a
1442
+ * patch that does not apply, so the version names the chain the content
1443
+ * is AT. Hashes nothing.
1444
+ */
1445
+ }, {
1446
+ key: "chainAt",
1447
+ value: function chainAt(path, count) {
1448
+ var _this$prefixes$get, _ref, _prefixes;
1449
+ if (count <= 0) return EMPTY_CHAIN;
1450
+ var prefixes = (_this$prefixes$get = this.prefixes.get(path)) !== null && _this$prefixes$get !== void 0 ? _this$prefixes$get : [];
1451
+ return (_ref = (_prefixes = prefixes[Math.min(count, prefixes.length) - 1]) !== null && _prefixes !== void 0 ? _prefixes : this.chains.get(path)) !== null && _ref !== void 0 ? _ref : EMPTY_CHAIN;
1452
+ }
1453
+
1454
+ /** See {@link epochValue}. */
1455
+ }, {
1456
+ key: "epoch",
1457
+ value: function epoch() {
1458
+ return "".concat(this.instance, "-").concat(this.epochValue);
1459
+ }
1460
+ }]);
1461
+ }();
1462
+
1463
+ /** {@link ModuleVersion} on the wire. */
1464
+ var ModuleVersionSchema = z.object({
1465
+ schema: z.string(),
1466
+ base: z.string(),
1467
+ chain: z.string()
1468
+ });
1469
+
1470
+ /** {@link ProjectVersion} on the wire. */
1471
+ var ProjectVersionSchema = z.object({
1472
+ build: z.string(),
1473
+ seq: z.number()
1474
+ });
1475
+
1305
1476
  /**
1306
1477
  * The wire shapes for reading history.
1307
1478
  *
@@ -1423,6 +1594,10 @@ var ModuleComparison = z.object({
1423
1594
  verdict: RestoreVerdict
1424
1595
  });
1425
1596
 
1597
+ /** An `EntryVersion` on the wire. */
1598
+ var EntryVersionSchema = ModuleVersionSchema.extend({
1599
+ key: z.string()
1600
+ });
1426
1601
  var ModuleFilePath = z.string().refine(function (_path) {
1427
1602
  return true;
1428
1603
  } // TODO: validation
@@ -2045,11 +2220,112 @@ var Api = {
2045
2220
  })])
2046
2221
  }
2047
2222
  },
2223
+ /**
2224
+ * What each module IS, right now: its version (`schema`, `base`, `chain`),
2225
+ * and the project version that is relative to. What `/stat` answers, per
2226
+ * module; see `docs/plans/one-content-model.md`, "The Studio loads
2227
+ * everything from one place".
2228
+ *
2229
+ * It has its own ETag, so a client that sends it back (`If-None-Match`)
2230
+ * gets a 304 and no body while nothing has moved. Computed from versions
2231
+ * kept as content changes: answering it hashes nothing.
2232
+ *
2233
+ * "Since `seq` N" (`?since=N`, with the ETag of the manifest the client
2234
+ * holds, which was answered at `seq` N, as `If-None-Match`): the modules
2235
+ * whose version moved since that manifest, and no others, with `since: N`
2236
+ * on the answer to say it is a delta. The ETag is in the body too (`tag`),
2237
+ * for a client that reads answers through `ValClient`, which hands over
2238
+ * the body and not the headers. Answered whole -- no `since` -- when the server no
2239
+ * longer holds the manifest that ETag names (another process, another
2240
+ * build, too long ago), and by a server of the release before, which
2241
+ * ignores the parameter. Optional both ways.
2242
+ */
2243
+ "/manifest": {
2244
+ GET: {
2245
+ req: {
2246
+ cookies: {
2247
+ val_session: z.string().optional()
2248
+ },
2249
+ headers: {
2250
+ "if-none-match": z.string().optional()
2251
+ },
2252
+ query: {
2253
+ since: onlyOneStringQueryParam.optional()
2254
+ }
2255
+ },
2256
+ res: z.union([unauthorizedResponse, z.object({
2257
+ status: z.literal(500),
2258
+ json: GenericError
2259
+ }), z.object({
2260
+ status: z.literal(200),
2261
+ json: z.object({
2262
+ project: ProjectVersionSchema,
2263
+ modules: z.record(ModuleFilePath, ModuleVersionSchema),
2264
+ /** This answer's ETag, as the header has it. */
2265
+ tag: z.string().optional(),
2266
+ /**
2267
+ * Present on a delta only: the `seq` of the manifest it is
2268
+ * relative to, whose modules that are not named here did not
2269
+ * move. Absent: every module is named.
2270
+ */
2271
+ since: z.number().optional(),
2272
+ /**
2273
+ * The build that answered is a previous build still answering
2274
+ * (http mode: the content service counted a newer one live).
2275
+ * Its versions are right for its own bases, which the site does
2276
+ * not serve any more: a Studio does not move to them, and asks
2277
+ * again. Optional: absent from a live build and a server of the
2278
+ * release before.
2279
+ */
2280
+ superseded: z["boolean"]().optional(),
2281
+ /**
2282
+ * The answering build's `val.modules` registry, as an entry a
2283
+ * Studio can import (`valModulesEntryPath`), and the Val it
2284
+ * runs: where a Studio opened on another build takes this
2285
+ * build's code in without reloading the page. Only a build the
2286
+ * platform made and names (`publishBuild`) has one. Optional
2287
+ * both ways: absent from any other server (local development,
2288
+ * where HMR brings the code; a self-hosted deploy) and from a
2289
+ * server of the release before, and ignored by a Studio of the
2290
+ * release before.
2291
+ */
2292
+ registry: z.object({
2293
+ entry: z.string(),
2294
+ core: z.string()
2295
+ }).optional()
2296
+ })
2297
+ }),
2298
+ /*
2299
+ * Nothing moved since the ETag the client sent. No body on the wire;
2300
+ * the client reads it as `json: null`.
2301
+ */
2302
+ z.object({
2303
+ status: z.literal(304),
2304
+ json: z["null"]().optional()
2305
+ })])
2306
+ }
2307
+ },
2048
2308
  "/stat": {
2049
2309
  POST: {
2050
2310
  req: {
2051
2311
  body: z.object({
2312
+ /**
2313
+ * @deprecated Echoed by the Studio, and compared by the fs and
2314
+ * memory long poll to see that another build answers. This
2315
+ * release's Studio reads each module's version from
2316
+ * `GET /manifest`; goes once the long poll compares the echoed
2317
+ * version instead (see "Release-delayed deletions" in
2318
+ * `docs/plans/one-content-model.md`).
2319
+ */
2052
2320
  sourcesSha: z.string(),
2321
+ /**
2322
+ * @deprecated Echoed by the Studio, and compared by the fs and
2323
+ * memory long poll to see that another build answers. This
2324
+ * release's Studio reads each module's version from
2325
+ * `GET /manifest`; goes once the long poll compares the echoed
2326
+ * version instead (see "Release-delayed deletions" in
2327
+ * `docs/plans/one-content-model.md`).
2328
+ */
2053
2329
  schemaSha: z.string(),
2054
2330
  baseSha: z.string(),
2055
2331
  patches: z.array(z.string()).optional(),
@@ -2079,7 +2355,21 @@ var Api = {
2079
2355
  json: z.union([z.object({
2080
2356
  type: z.union([z.literal("request-again"), z.literal("no-change"), z.literal("did-change")]),
2081
2357
  baseSha: z.string(),
2358
+ /**
2359
+ * @deprecated Read by a Studio of the release before
2360
+ * (`BaseAlignment`, `SchemaFreshnessWatch`); this release's
2361
+ * only echoes it, and reads each module's version from
2362
+ * `GET /manifest`. Goes in two steps (see "Release-delayed
2363
+ * deletions" in `docs/plans/one-content-model.md`).
2364
+ */
2082
2365
  schemaSha: z.string(),
2366
+ /**
2367
+ * @deprecated Read by a Studio of the release before
2368
+ * (`BaseAlignment`, `SchemaFreshnessWatch`); this release's
2369
+ * only echoes it, and reads each module's version from
2370
+ * `GET /manifest`. Goes in two steps (see "Release-delayed
2371
+ * deletions" in `docs/plans/one-content-model.md`).
2372
+ */
2083
2373
  sourcesSha: z.string(),
2084
2374
  patches: z.array(PatchId),
2085
2375
  /**
@@ -2177,13 +2467,34 @@ var Api = {
2177
2467
  * the git mirror's or the one content reports for the project.
2178
2468
  * Absent where there is no history to show.
2179
2469
  */
2180
- historyBranch: z.string().optional()
2470
+ historyBranch: z.string().optional(),
2471
+ /**
2472
+ * What this answer is relative to: the build that served it
2473
+ * and how far along the project's changes it is (`seq`). Of two
2474
+ * answers, the higher `seq` is the newer. Optional: a server
2475
+ * that predates versions sends none.
2476
+ */
2477
+ version: ProjectVersionSchema.optional()
2181
2478
  }), z.object({
2182
2479
  type: z.literal("use-websocket"),
2183
2480
  url: z.string(),
2184
2481
  nonce: z.string(),
2185
2482
  baseSha: z.string(),
2483
+ /**
2484
+ * @deprecated Read by a Studio of the release before
2485
+ * (`BaseAlignment`, `SchemaFreshnessWatch`); this release's
2486
+ * only echoes it, and reads each module's version from
2487
+ * `GET /manifest`. Goes in two steps (see "Release-delayed
2488
+ * deletions" in `docs/plans/one-content-model.md`).
2489
+ */
2186
2490
  schemaSha: z.string(),
2491
+ /**
2492
+ * @deprecated Read by a Studio of the release before
2493
+ * (`BaseAlignment`, `SchemaFreshnessWatch`); this release's
2494
+ * only echoes it, and reads each module's version from
2495
+ * `GET /manifest`. Goes in two steps (see "Release-delayed
2496
+ * deletions" in `docs/plans/one-content-model.md`).
2497
+ */
2187
2498
  sourcesSha: z.string(),
2188
2499
  /**
2189
2500
  * The commit this deployment was built from.
@@ -2305,7 +2616,14 @@ var Api = {
2305
2616
  * the git mirror's or the one content reports for the project.
2306
2617
  * Absent where there is no history to show.
2307
2618
  */
2308
- historyBranch: z.string().optional()
2619
+ historyBranch: z.string().optional(),
2620
+ /**
2621
+ * What this answer is relative to: the build that served it
2622
+ * and how far along the project's changes it is (`seq`). Of two
2623
+ * answers, the higher `seq` is the newer. Optional: a server
2624
+ * that predates versions sends none.
2625
+ */
2626
+ version: ProjectVersionSchema.optional()
2309
2627
  })])
2310
2628
  })])
2311
2629
  }
@@ -2347,6 +2665,14 @@ var Api = {
2347
2665
  path: z.string().optional(),
2348
2666
  cookies: {
2349
2667
  val_session: z.string().optional()
2668
+ },
2669
+ /*
2670
+ * Carried to content, which answers 304 when it names the answer
2671
+ * (`/project-source` does: valbuild/home sends its `ETag`). The
2672
+ * `ETag` comes back as a header, and a 304 with no body.
2673
+ */
2674
+ headers: {
2675
+ "if-none-match": z.string().optional()
2350
2676
  }
2351
2677
  },
2352
2678
  /*
@@ -2358,10 +2684,18 @@ var Api = {
2358
2684
  * `{ message }` rather than throwing, because a publish failing is
2359
2685
  * something the editor has to be told about in words.
2360
2686
  */
2361
- res: z.object({
2687
+ res: z.union([z.object({
2362
2688
  status: z.number(),
2363
2689
  json: z.unknown()
2364
- })
2690
+ }),
2691
+ /*
2692
+ * Content said the caller holds the answer already (`If-None-Match`).
2693
+ * No body on the wire: a 304 cannot carry one.
2694
+ */
2695
+ z.object({
2696
+ status: z.literal(304),
2697
+ json: z["null"]().optional()
2698
+ })])
2365
2699
  },
2366
2700
  POST: {
2367
2701
  req: {
@@ -2797,7 +3131,9 @@ var Api = {
2797
3131
  })),
2798
3132
  baseSha: z.string(),
2799
3133
  error: GenericError.optional(),
2800
- errors: z.record(PatchId, GenericError).optional()
3134
+ errors: z.record(PatchId, GenericError).optional(),
3135
+ /** The project version these patches were read at. See `/stat`. */
3136
+ version: ProjectVersionSchema.optional()
2801
3137
  })
2802
3138
  })])
2803
3139
  }
@@ -2830,6 +3166,15 @@ var Api = {
2830
3166
  })])
2831
3167
  }
2832
3168
  },
3169
+ /**
3170
+ * @deprecated The whole tree. Nothing of this release asks it: the Studio
3171
+ * reads modules by version (`GET /manifest`, `GET /modules/~`), the draft
3172
+ * readers and MCP read the head in-process. Kept, with `exclude_patches`
3173
+ * (`BaseAlignment`'s fetch) and the ignored `validate_sources` and
3174
+ * `validate_binary_files`, for a Studio of the release before; goes in the
3175
+ * release after the one that ships this (see "Release-delayed deletions"
3176
+ * in `docs/plans/one-content-model.md`).
3177
+ */
2833
3178
  "/sources/~": {
2834
3179
  PUT: {
2835
3180
  req: {
@@ -2881,12 +3226,100 @@ var Api = {
2881
3226
  skipped: z.array(PatchId).optional(),
2882
3227
  errors: z.record(PatchId, GenericError).optional()
2883
3228
  }).optional(),
2884
- validationErrors: z.record(z.string(), z.array(ValidationError)).optional()
2885
- }))
3229
+ validationErrors: z.record(z.string(), z.array(ValidationError)).optional(),
3230
+ /**
3231
+ * The version of `source` as answered: with the chain the
3232
+ * server applied, or the empty chain when it applied none
3233
+ * (`apply_patches=false`). Optional: an older server sends
3234
+ * none.
3235
+ */
3236
+ version: ModuleVersionSchema.optional()
3237
+ })),
3238
+ /** The project version this tree was read at. See `/stat`. */
3239
+ project: ProjectVersionSchema.optional()
2886
3240
  })
2887
3241
  })])
2888
3242
  }
2889
3243
  },
3244
+ /**
3245
+ * One module by version: phase 6 of `docs/plans/one-content-model.md`
3246
+ * ("The Studio loads everything from one place"). `GET /modules/~<path>`,
3247
+ * with `v` the version wanted as its `moduleKey` (the newest when absent;
3248
+ * a base is a version with the empty chain) and `have` the version the
3249
+ * caller holds: when the caller's chain is a start of the answer's under
3250
+ * the same schema and base, the answer is the patches from it (`delta`),
3251
+ * otherwise the whole module. An answer for an asked `v` is immutable.
3252
+ */
3253
+ "/modules/~": {
3254
+ GET: {
3255
+ req: {
3256
+ path: z.string(),
3257
+ query: {
3258
+ v: onlyOneStringQueryParam.optional(),
3259
+ have: onlyOneStringQueryParam.optional()
3260
+ },
3261
+ cookies: {
3262
+ val_session: z.string().optional()
3263
+ }
3264
+ },
3265
+ res: z.union([unauthorizedResponse, z.object({
3266
+ status: z.literal(404),
3267
+ json: z.object({
3268
+ message: z.string(),
3269
+ /** The version held now, when the one asked for is not. */
3270
+ current: ModuleVersionSchema.optional()
3271
+ })
3272
+ }), z.object({
3273
+ status: z.literal(500),
3274
+ json: GenericError
3275
+ }), z.object({
3276
+ status: z.literal(200),
3277
+ headers: z.object({
3278
+ "Cache-Control": z.string()
3279
+ }).optional(),
3280
+ json: z.union([z.object({
3281
+ kind: z.literal("module"),
3282
+ version: ModuleVersionSchema,
3283
+ source: z.any(),
3284
+ /**
3285
+ * The `.jsonValues()` entries the module's chain touched, read,
3286
+ * each with its own version. Every other entry is the base's,
3287
+ * behind its marker in `source`.
3288
+ */
3289
+ entries: z.record(z.string(), z.union([z.object({
3290
+ content: z.any(),
3291
+ version: EntryVersionSchema
3292
+ }), z.object({
3293
+ deleted: z.literal(true),
3294
+ version: EntryVersionSchema
3295
+ }), z.object({
3296
+ error: z.string(),
3297
+ /** The patch that made it unreadable, when known. */
3298
+ patchId: PatchId.optional(),
3299
+ version: EntryVersionSchema
3300
+ })])),
3301
+ /**
3302
+ * The patch ids its chain is made of, in chain order: what a
3303
+ * client holding this knows the server already has. Optional:
3304
+ * a server of the release before sends none.
3305
+ */
3306
+ patches: z.array(PatchId).optional(),
3307
+ failed: z.object({
3308
+ patchId: PatchId,
3309
+ message: z.string()
3310
+ }).optional()
3311
+ }), z.object({
3312
+ kind: z.literal("delta"),
3313
+ from: ModuleVersionSchema,
3314
+ to: ModuleVersionSchema,
3315
+ patches: z.array(z.object({
3316
+ patchId: PatchId,
3317
+ patch: Patch
3318
+ }))
3319
+ })])
3320
+ })])
3321
+ }
3322
+ },
2890
3323
  /**
2891
3324
  * The Studio's full check, one module per request: its sources against its
2892
3325
  * schema and its local files against their bytes, on the content the caller
@@ -3607,7 +4040,14 @@ var Api = {
3607
4040
  path: ModuleFilePath,
3608
4041
  key: z.string(),
3609
4042
  // The entry's JSON content (or null if the entry has no value).
3610
- content: z.any()
4043
+ content: z.any(),
4044
+ /**
4045
+ * The version of the record module the entry was read from, with
4046
+ * the chain that was applied (the empty chain without
4047
+ * `apply_patches`). An entry's own version comes with entries
4048
+ * stored as modules of their own.
4049
+ */
4050
+ version: ModuleVersionSchema.optional()
3611
4051
  })
3612
4052
  }),
3613
4053
  // Shapes (2) and (3): `keys` or `offset`+`limit`.
@@ -3630,7 +4070,9 @@ var Api = {
3630
4070
  // All-mode only: the resolved window and the record's total key count.
3631
4071
  offset: z.number().optional(),
3632
4072
  limit: z.number().optional(),
3633
- total: z.number().optional()
4073
+ total: z.number().optional(),
4074
+ /** As for a single `key`. */
4075
+ version: ModuleVersionSchema.optional()
3634
4076
  })
3635
4077
  }), z.object({
3636
4078
  status: z.literal(400),
@@ -4093,8 +4535,8 @@ var createValClient = function createValClient(host, config, options) {
4093
4535
  body: anyReq.body !== undefined ? JSON.stringify(anyReq.body) : undefined
4094
4536
  }).then(/*#__PURE__*/function () {
4095
4537
  var _ref2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(res) {
4096
- var _apiEndpoint$res;
4097
- var contentTypeHeaders, json, valClientResult, message, responseResult;
4538
+ var _apiEndpoint$res2;
4539
+ var contentTypeHeaders, _apiEndpoint$res, notModified, json, valClientResult, message, responseResult;
4098
4540
  return _regenerator().w(function (_context) {
4099
4541
  while (1) switch (_context.n) {
4100
4542
  case 0:
@@ -4112,10 +4554,38 @@ var createValClient = function createValClient(host, config, options) {
4112
4554
  }
4113
4555
  });
4114
4556
  case 1:
4115
- if (contentTypeHeaders !== null && contentTypeHeaders !== void 0 && contentTypeHeaders.includes("application/json")) {
4557
+ if (!(res.status === 304)) {
4558
+ _context.n = 3;
4559
+ break;
4560
+ }
4561
+ notModified = {
4562
+ status: 304,
4563
+ json: null
4564
+ };
4565
+ if (!(((_apiEndpoint$res = apiEndpoint.res) === null || _apiEndpoint$res === void 0 ? void 0 : _apiEndpoint$res.safeParse(notModified).success) === false)) {
4116
4566
  _context.n = 2;
4117
4567
  break;
4118
4568
  }
4569
+ return _context.a(2, {
4570
+ status: null,
4571
+ json: {
4572
+ type: "client_side_validation_error",
4573
+ message: "Invalid response (304 from a route that does not answer one). This could be a result of mismatched Val versions.",
4574
+ details: {
4575
+ validationError: "Unexpected 304",
4576
+ data: {
4577
+ status: res.status
4578
+ }
4579
+ }
4580
+ }
4581
+ });
4582
+ case 2:
4583
+ return _context.a(2, notModified);
4584
+ case 3:
4585
+ if (contentTypeHeaders !== null && contentTypeHeaders !== void 0 && contentTypeHeaders.includes("application/json")) {
4586
+ _context.n = 4;
4587
+ break;
4588
+ }
4119
4589
  return _context.a(2, {
4120
4590
  status: null,
4121
4591
  json: {
@@ -4130,17 +4600,17 @@ var createValClient = function createValClient(host, config, options) {
4130
4600
  }
4131
4601
  }
4132
4602
  });
4133
- case 2:
4134
- _context.n = 3;
4603
+ case 4:
4604
+ _context.n = 5;
4135
4605
  return res.json();
4136
- case 3:
4606
+ case 5:
4137
4607
  json = _context.v;
4138
4608
  valClientResult = {
4139
4609
  status: res.status,
4140
4610
  json: json
4141
4611
  };
4142
4612
  if (!(res.status === 500)) {
4143
- _context.n = 4;
4613
+ _context.n = 6;
4144
4614
  break;
4145
4615
  }
4146
4616
  Internal.log.error("Server responded with an error", json);
@@ -4152,10 +4622,10 @@ var createValClient = function createValClient(host, config, options) {
4152
4622
  message: message
4153
4623
  }
4154
4624
  });
4155
- case 4:
4156
- responseResult = (_apiEndpoint$res = apiEndpoint.res) === null || _apiEndpoint$res === void 0 ? void 0 : _apiEndpoint$res.safeParse(valClientResult);
4625
+ case 6:
4626
+ responseResult = (_apiEndpoint$res2 = apiEndpoint.res) === null || _apiEndpoint$res2 === void 0 ? void 0 : _apiEndpoint$res2.safeParse(valClientResult);
4157
4627
  if (!(responseResult && !responseResult.success)) {
4158
- _context.n = 5;
4628
+ _context.n = 7;
4159
4629
  break;
4160
4630
  }
4161
4631
  return _context.a(2, {
@@ -4169,7 +4639,7 @@ var createValClient = function createValClient(host, config, options) {
4169
4639
  }
4170
4640
  }
4171
4641
  });
4172
- case 5:
4642
+ case 7:
4173
4643
  return _context.a(2, {
4174
4644
  status: res.status,
4175
4645
  json: json
@@ -4313,6 +4783,44 @@ var urlOf = function urlOf() {
4313
4783
  return "".concat(route);
4314
4784
  };
4315
4785
 
4786
+ /**
4787
+ * Which Val package, at which version, sent a request to Val Build.
4788
+ *
4789
+ * Sent on every request a Val package makes to Val's own services from a
4790
+ * server or a CLI -- the content host, `/api/login`, the analytics ingest --
4791
+ * so Val Build can tell which versions are still out there and which old
4792
+ * endpoints they still call. Without it an endpoint can never be retired with
4793
+ * any confidence: a request from a site nobody has upgraded in a year looks
4794
+ * exactly like one from yesterday's release.
4795
+ *
4796
+ * The value is a product token, `@valbuild/server@0.152.0`: the package whose
4797
+ * code built the request, because that is what decides which endpoints are
4798
+ * called. Not the framework binding or the app: those do not choose URLs.
4799
+ *
4800
+ * NOT sent from the browser. A custom header on a cross-origin request makes
4801
+ * the browser send a CORS preflight, and a host that does not list the header
4802
+ * in `Access-Control-Allow-Headers` refuses the request outright -- so adding
4803
+ * it there is a change to every such host first.
4804
+ *
4805
+ * It identifies the code, not the caller: anyone can send any value, so it is
4806
+ * for counting, never for deciding what a request may do.
4807
+ */
4808
+ var VAL_CLIENT_HEADER = "x-val-client";
4809
+
4810
+ /** Packages that send {@link VAL_CLIENT_HEADER}. */
4811
+
4812
+ /**
4813
+ * `{ "x-val-client": "@valbuild/server@0.152.0" }`, to spread into a
4814
+ * request's headers.
4815
+ *
4816
+ * A version that could not be read is sent as `unknown` rather than leaving
4817
+ * the header out: "a server too broken to know its version" is still a thing
4818
+ * worth counting, and it is not the same as "a version older than the header".
4819
+ */
4820
+ function valClientHeaders(pkg, version) {
4821
+ return _defineProperty({}, VAL_CLIENT_HEADER, "".concat(pkg, "@").concat(version || "unknown"));
4822
+ }
4823
+
4316
4824
  /**
4317
4825
  * Can this value, taken from one schema, be written into another?
4318
4826
  *
@@ -4746,23 +5254,1517 @@ function chainHeadOf(reportedHead, listed) {
4746
5254
  return (_listed = listed[listed.length - 1]) !== null && _listed !== void 0 ? _listed : null;
4747
5255
  }
4748
5256
 
4749
- var SharedValConfig = z.object({
4750
- contentHostUrl: z.string().optional(),
4751
- appHostUrl: z.string().optional(),
4752
- project: z.string().optional(),
4753
- root: z.string().optional(),
4754
- gitCommit: z.string().optional(),
4755
- gitBranch: z.string().optional(),
4756
- defaultTheme: z["enum"](["dark", "light"]).optional(),
4757
- ai: z.object({
4758
- commitMessages: z.object({
4759
- disabled: z["boolean"]().optional()
4760
- }).optional()
4761
- }).optional()
4762
- });
4763
-
4764
5257
  /**
4765
- * Config that is shared between the client and server.
5258
+ * Where a build's `val.modules` registry can be imported from: phase 9 of
5259
+ * `docs/plans/one-content-model.md`, "Reloading the schema while the Studio
5260
+ * runs".
5261
+ *
5262
+ * A build made by `@valbuild/tanstack-build` (every build the platform
5263
+ * serves) emits its `val.modules` as an ES module of its own beside its
5264
+ * client chunks, named after the build: `/_app/val-modules-<build>.js`,
5265
+ * whose default export is the registry. The server running that build knows
5266
+ * its name (`VAL_BUILD`, Val's `publishBuild`) and names the entry on
5267
+ * `GET /manifest` (`registry`), so a Studio opened on an older build imports
5268
+ * the new one's code and reloads it in place (`HostStore.reloadCode`)
5269
+ * instead of being told to reload the page.
5270
+ *
5271
+ * The convention is the builder's and this file's together: the builder
5272
+ * writes the file (`valModulesEntryFile` in `@valbuild/tanstack-build`, which
5273
+ * does not depend on this package), and each side pins the same literal,
5274
+ * `val-modules-<build>.js` (`buildRegistry.test.ts` here, `valModules.test.ts`
5275
+ * there).
5276
+ */
5277
+
5278
+ /** The file a build's registry is emitted as, among its client chunks. */
5279
+ function valModulesEntryFile(build) {
5280
+ return "val-modules-".concat(build, ".js");
5281
+ }
5282
+
5283
+ /** Where a build served by the platform serves its registry. */
5284
+ function valModulesEntryPath(build) {
5285
+ return "/_app/".concat(valModulesEntryFile(build));
5286
+ }
5287
+
5288
+ /**
5289
+ * What `GET /manifest` says about the code of the build that answered:
5290
+ * where its registry is, and which Val it runs. A Studio running another
5291
+ * Val cannot take the build's code in (its own stores would run against a
5292
+ * new core), so it reloads itself instead (`other-val-version`).
5293
+ */
5294
+
5295
+ /*
5296
+ * How a patch op relates to a `.jsonValues()` record: which ops reach into an
5297
+ * entry, what a write of the whole record says about each entry, and how an
5298
+ * op inside an entry is rebased onto the entry's own file.
5299
+ *
5300
+ * Here, in shared, because the engine (`applyChain.ts`) is built on them and
5301
+ * runs wherever content is patched; the commit flow (`ValOps.prepare`), the
5302
+ * CLI's fixes and the language server use the same functions, through
5303
+ * `@valbuild/server`, which re-exports them. One rule, one implementation:
5304
+ * a draft that shows one thing while publishing writes another is the failure
5305
+ * nobody sees until it is published.
5306
+ */
5307
+
5308
+ /**
5309
+ * Classification of a single patch op against a module's serialized schema,
5310
+ * used by the commit flow to route ops for `.jsonValues()` records:
5311
+ *
5312
+ * - `normal`: the op does not descend into a `.jsonValues()` entry; apply it to
5313
+ * the `.val.ts` as usual.
5314
+ * - `entry`: the op targets a `.jsonValues()` entry. `recordPath` is the path to
5315
+ * the record within the module source (empty for a root record/router),
5316
+ * `entryKey` is the entry key, and `subPath` is the remaining path inside the
5317
+ * entry (empty when the op targets the entry value itself, e.g. add/remove of
5318
+ * the whole entry).
5319
+ */
5320
+
5321
+ /**
5322
+ * Walks the serialized schema following the op path. When a `.jsonValues()`
5323
+ * record is encountered, the next path segment is the entry key and everything
5324
+ * after it lives inside the entry's `*.val.json` (so it does not touch the
5325
+ * `.val.ts`). Returns `{ kind: "normal" }` when the op never enters a
5326
+ * `.jsonValues()` record.
5327
+ */
5328
+ function classifyJsonValuesOp(schema, opPath) {
5329
+ var current = schema;
5330
+ var recordPath = [];
5331
+ for (var i = 0; i < opPath.length; i++) {
5332
+ if (!current) {
5333
+ return {
5334
+ kind: "normal"
5335
+ };
5336
+ }
5337
+ if (current.type === "record" && current.jsonValues) {
5338
+ return {
5339
+ kind: "entry",
5340
+ recordPath: recordPath.slice(),
5341
+ entryKey: opPath[i],
5342
+ subPath: opPath.slice(i + 1)
5343
+ };
5344
+ }
5345
+ var seg = opPath[i];
5346
+ current = descend(current, seg);
5347
+ recordPath.push(seg);
5348
+ }
5349
+ return {
5350
+ kind: "normal"
5351
+ };
5352
+ }
5353
+ function descend(schema, key) {
5354
+ switch (schema.type) {
5355
+ case "object":
5356
+ return schema.items[key];
5357
+ case "record":
5358
+ return schema.item;
5359
+ case "array":
5360
+ return schema.item;
5361
+ default:
5362
+ // Unions / primitives / leaf schemas: we cannot (or need not) descend
5363
+ // further to find a jsonValues record. Anything below is a normal
5364
+ // `.val.ts` edit.
5365
+ return undefined;
5366
+ }
5367
+ }
5368
+
5369
+ /**
5370
+ * Is this op a write of the WHOLE record at the root of a `.jsonValues()`
5371
+ * module?
5372
+ *
5373
+ * The one op {@link classifyJsonValuesOp} cannot classify: it finds the entry
5374
+ * key by walking the op path, and the root path has no segments to walk, so a
5375
+ * root write reads as `normal` and gets applied to the `.val.ts` - writing
5376
+ * markers over the `c.json(() => import(...))` calls that make the entries load
5377
+ * at all. {@link expandJsonValuesRootOp} turns it into ops that DO name a key,
5378
+ * which everything downstream already handles.
5379
+ *
5380
+ * `add` counts as well as `replace`: at the root both mean "the document is now
5381
+ * this" (see `JSONOps`), so both have to be expanded or the unexpanded one is
5382
+ * the same bug again.
5383
+ *
5384
+ * The value has to BE a record, and that is part of the question rather than a
5385
+ * check inside the expansion: a `.jsonValues()` record can be nullable, and
5386
+ * `null` at the root means the module no longer has a record at all - there are
5387
+ * no entries to write, and it is an ordinary `.val.ts` write, exactly as it was
5388
+ * before any of this existed. Same for any other non-record value: not a
5389
+ * whole-record write, so not this conversion's to route.
5390
+ */
5391
+ function isJsonValuesRootOp(schema, op) {
5392
+ return (op.op === "replace" || op.op === "add") && op.path.length === 0 && isJsonValuesRecord(op.value) && schema.type === "record" && schema.jsonValues === true;
5393
+ }
5394
+ function isJsonValuesRecord(value) {
5395
+ return _typeof(value) === "object" && value !== null && !Array.isArray(value);
5396
+ }
5397
+
5398
+ /**
5399
+ * One entry as it stands right now, for {@link expandJsonValuesRootOp} to
5400
+ * expand against.
5401
+ *
5402
+ * The key set decides add-vs-replace-vs-remove. The content, where the caller
5403
+ * has it, decides whether an op is emitted at all: an entry whose content is
5404
+ * already what the root write says is left alone rather than rewritten, so
5405
+ * putting a module back does not touch every `*.val.json` it did not change.
5406
+ * `undefined` means "this entry exists, content unknown" - which is what the
5407
+ * Studio's draft source has, because the source holds markers - and yields a
5408
+ * `replace`, the safe answer.
5409
+ */
5410
+
5411
+ /**
5412
+ * Fans a whole-record write at a `.jsonValues()` module's root out into ops
5413
+ * that each name an entry key.
5414
+ *
5415
+ * This is THE conversion, and it has exactly one implementation on purpose: the
5416
+ * commit flow (`ValOps.prepare`) and the engine that builds draft content
5417
+ * (`applyPatchRecord`) both expand through here, so a draft
5418
+ * cannot show something other than what publishing writes. A second
5419
+ * implementation of the same rule would differ silently, which is the whole
5420
+ * failure mode this exists to prevent.
5421
+ *
5422
+ * The value must be the entries' CONTENT. A module's Source is markers, not
5423
+ * content, so a Source handed over here is refused rather than written: that is
5424
+ * the shape of the original bug (a revert replaying the archived Source), and
5425
+ * it is not recoverable afterwards - the markers replace the entries' content
5426
+ * on disk.
5427
+ */
5428
+ function expandJsonValuesRootOp(op, currentEntries) {
5429
+ var valueRes = jsonValuesRootValue(op);
5430
+ if (result.isErr(valueRes)) {
5431
+ return valueRes;
5432
+ }
5433
+ var value = valueRes.value;
5434
+ var expanded = [];
5435
+ for (var _i = 0, _Object$keys = Object.keys(value); _i < _Object$keys.length; _i++) {
5436
+ var entryKey = _Object$keys[_i];
5437
+ var write = writeForEntry(value, entryKey, currentEntries);
5438
+ if (write !== null) {
5439
+ expanded.push(write);
5440
+ }
5441
+ }
5442
+ var _iterator = _createForOfIteratorHelper(currentEntries.keys()),
5443
+ _step;
5444
+ try {
5445
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
5446
+ var _entryKey = _step.value;
5447
+ if (!Object.prototype.hasOwnProperty.call(value, _entryKey)) {
5448
+ expanded.push({
5449
+ op: "remove",
5450
+ path: [_entryKey]
5451
+ });
5452
+ }
5453
+ }
5454
+ } catch (err) {
5455
+ _iterator.e(err);
5456
+ } finally {
5457
+ _iterator.f();
5458
+ }
5459
+ return result.ok(expanded);
5460
+ }
5461
+
5462
+ /**
5463
+ * What a whole-record write says about ONE entry: the same rule
5464
+ * {@link expandJsonValuesRootOp} applies to all of them.
5465
+ *
5466
+ * For a reader that is about one entry. Expanding the whole record to find the
5467
+ * one op that names its key costs an op per entry per entry read - the record
5468
+ * squared, on exactly the large records this feature is for. The rule itself is
5469
+ * `writeForEntry`, shared with the expansion above, so the two cannot come to
5470
+ * disagree; only the loop around it differs.
5471
+ *
5472
+ * `null` means the write says nothing about this entry: it holds that content
5473
+ * already, or the record does not name it and it does not exist.
5474
+ */
5475
+ function expandJsonValuesRootOpForKey(op, entryKey, currentEntries) {
5476
+ var valueRes = jsonValuesRootValue(op);
5477
+ if (result.isErr(valueRes)) {
5478
+ return valueRes;
5479
+ }
5480
+ var value = valueRes.value;
5481
+ if (Object.prototype.hasOwnProperty.call(value, entryKey)) {
5482
+ return result.ok(writeForEntry(value, entryKey, currentEntries));
5483
+ }
5484
+ if (currentEntries.has(entryKey)) {
5485
+ return result.ok({
5486
+ op: "remove",
5487
+ path: [entryKey]
5488
+ });
5489
+ }
5490
+ return result.ok(null);
5491
+ }
5492
+
5493
+ /**
5494
+ * The record a whole-record write carries, once it is known to be one.
5495
+ *
5496
+ * Every entry's value is checked here rather than where each one is written:
5497
+ * a marker anywhere in the record means the writer handed over a module's
5498
+ * Source, and the answer to that is to refuse the write, not to write the rest
5499
+ * of it. Both entry points ask, so an entry read reports it too rather than
5500
+ * showing a draft of a patch that publishing will refuse.
5501
+ */
5502
+ function jsonValuesRootValue(op) {
5503
+ if (op.op !== "replace" && op.op !== "add") {
5504
+ return result.err(new PatchError("Cannot '".concat(op.op, "' the root of a .jsonValues() record: only add and replace write the whole record")));
5505
+ }
5506
+ var value = op.value;
5507
+ if (!isJsonValuesRecord(value)) {
5508
+ return result.err(new PatchError("Cannot write the root of a .jsonValues() record with a non-record value: it must be an object of entry key to entry content"));
5509
+ }
5510
+ for (var _i2 = 0, _Object$keys2 = Object.keys(value); _i2 < _Object$keys2.length; _i2++) {
5511
+ var entryKey = _Object$keys2[_i2];
5512
+ /*
5513
+ * A marker is what a module's Source holds WHERE THE CONTENT IS NOT: it
5514
+ * says the content is in the entry's own file. Handed back as an entry's
5515
+ * value - which is what replaying an archived Source does - it would
5516
+ * replace that content with the note saying where it used to be, and
5517
+ * nothing downstream could tell the difference.
5518
+ */
5519
+ if (Internal.isJson(value[entryKey])) {
5520
+ return result.err(new PatchError("Cannot write the .jsonValues() entry '".concat(entryKey, "' from a '").concat(VAL_EXTENSION, ": \"json\"' marker: a marker says the content is in the entry's own file, so write the entry's CONTENT here, not the module's Source")));
5521
+ }
5522
+ }
5523
+ return result.ok(value);
5524
+ }
5525
+
5526
+ /** THE rule, for one entry the record names. `null` = nothing to write. */
5527
+ function writeForEntry(value, entryKey, currentEntries) {
5528
+ var content = value[entryKey];
5529
+ if (!currentEntries.has(entryKey)) {
5530
+ return {
5531
+ op: "add",
5532
+ path: [entryKey],
5533
+ value: content
5534
+ };
5535
+ }
5536
+ var current = currentEntries.get(entryKey);
5537
+ if (current !== undefined && deepEqual(current, content)) {
5538
+ // Unchanged: emitting a replace here would rewrite the entry's file with
5539
+ // the bytes it already holds, so putting a module back would show up as a
5540
+ // change to every entry in it.
5541
+ return null;
5542
+ }
5543
+ return {
5544
+ op: "replace",
5545
+ path: [entryKey],
5546
+ value: content
5547
+ };
5548
+ }
5549
+
5550
+ /**
5551
+ * Rebases a patch op that targets a `.jsonValues()` entry's content so its paths
5552
+ * are relative to the entry's `*.val.json` root (drops the record + entry-key
5553
+ * prefix). Used to replay the op against the backing JSON file.
5554
+ */
5555
+ function rebaseContentOp(op, prefixLen) {
5556
+ var path = op.path.slice(prefixLen);
5557
+ switch (op.op) {
5558
+ case "add":
5559
+ case "replace":
5560
+ case "test":
5561
+ return result.ok(_objectSpread2(_objectSpread2({}, op), {}, {
5562
+ path: path
5563
+ }));
5564
+ case "remove":
5565
+ {
5566
+ if (!array.isNonEmpty(path)) {
5567
+ return result.err(new PatchError("Cannot remove the root of a jsonValues entry"));
5568
+ }
5569
+ return result.ok(_objectSpread2(_objectSpread2({}, op), {}, {
5570
+ path: path
5571
+ }));
5572
+ }
5573
+ case "move":
5574
+ {
5575
+ var from = op.from.slice(prefixLen);
5576
+ if (!array.isNonEmpty(from)) {
5577
+ return result.err(new PatchError("Cannot move from the root of a jsonValues entry"));
5578
+ }
5579
+ return result.ok(_objectSpread2(_objectSpread2({}, op), {}, {
5580
+ path: path,
5581
+ from: from
5582
+ }));
5583
+ }
5584
+ case "copy":
5585
+ return result.ok(_objectSpread2(_objectSpread2({}, op), {}, {
5586
+ path: path,
5587
+ from: op.from.slice(prefixLen)
5588
+ }));
5589
+ case "file":
5590
+ // A file op carries bytes, not content. The binary goes through the
5591
+ // normal upload pipeline; what lands in the entry is the `patch_id` that
5592
+ // marks the bytes as not yet committed, which the caller writes.
5593
+ return result.err(new PatchError("A file op is not a content op: rebase it via its path"));
5594
+ }
5595
+ }
5596
+
5597
+ /**
5598
+ * The entry a path names when it names a WHOLE entry of the root
5599
+ * `.jsonValues()` record, and `undefined` for anything else.
5600
+ */
5601
+ function wholeEntryKey(serializedSchema, path) {
5602
+ if (!serializedSchema) {
5603
+ return undefined;
5604
+ }
5605
+ var cls = classifyJsonValuesOp(serializedSchema, path);
5606
+ return cls.kind === "entry" && cls.recordPath.length === 0 && cls.subPath.length === 0 ? cls.entryKey : undefined;
5607
+ }
5608
+
5609
+ /*
5610
+ * THE engine: one function that turns a module's content and a patch into
5611
+ * the module's next content. See `docs/plans/one-content-model.md`, "One
5612
+ * engine".
5613
+ *
5614
+ * It owns the rules the server's two readers used to follow each in their own
5615
+ * way (`ValOps.getSources` for the module, and a replay of its own for a
5616
+ * `.jsonValues()` entry, kept test-only in `legacyEngine.ts` in
5617
+ * `@valbuild/server` for the conformance cases):
5618
+ *
5619
+ * - where a file's `patch_id` lands: the field's path plus `nestedFilePath`,
5620
+ * after every other op of the patch;
5621
+ * - what an edit inside an entry does: applied to the entry's content when it
5622
+ * is loaded, held (`waiting`) until it is when it is not;
5623
+ * - what a whole entry added does: a marker in the module, content in the
5624
+ * entry;
5625
+ * - that a module's chain stops at its first failing patch, and that the
5626
+ * entries stop with it.
5627
+ *
5628
+ * Copy-on-write along each op's path: nothing is mutated, so a subtree no op
5629
+ * touched keeps its identity (which makes "did this path change?" a `===`),
5630
+ * and a patch costs the depth of its path, not the module's size. Every
5631
+ * container it copies is counted ({@link engineWork}).
5632
+ */
5633
+
5634
+ /** What one `.jsonValues()` entry is at a point in a module's chain. */
5635
+
5636
+ /**
5637
+ * The ops held for an entry, newest first: a list that is extended without
5638
+ * copying, so a chain of N edits to an entry nobody loaded costs N, not N².
5639
+ */
5640
+
5641
+ /**
5642
+ * A module's content: its source, and what its `.jsonValues()` entries are.
5643
+ *
5644
+ * `entries` holds only the entries the chain has touched. An entry it has
5645
+ * not is the base's: `waiting` on its own key with nothing held, as long as
5646
+ * the source has its marker ({@link entryState}). So a module with no
5647
+ * entries, or a chain that never touches one, costs nothing here.
5648
+ */
5649
+
5650
+ /** One patch of a module's chain. */
5651
+
5652
+ var NO_ENTRIES = new Map();
5653
+
5654
+ /** A module's content with nothing applied yet: its base. */
5655
+ function baseModuleContent(source) {
5656
+ return {
5657
+ source: source,
5658
+ entries: NO_ENTRIES
5659
+ };
5660
+ }
5661
+
5662
+ // #region counted work
5663
+
5664
+ /**
5665
+ * What a copied container costs, per slot copied (an object's entry or an
5666
+ * array's element): one reference. Strings and numbers are shared, never
5667
+ * copied, so this is what a copy really moves -- and what the whole module
5668
+ * cost per patch before copy-on-write (`applyPatch(deepClone(module))`).
5669
+ */
5670
+ var SLOT_BYTES = 8;
5671
+ var applies = 0;
5672
+ var clonedBytes = 0;
5673
+
5674
+ /**
5675
+ * The work the engine has done in this process: patches applied
5676
+ * (`engine:apply-patch`) and bytes copied (`engine:clone-bytes`, see
5677
+ * {@link SLOT_BYTES}). A test takes it before and after; never reset, so two
5678
+ * readers of it cannot disturb each other.
5679
+ */
5680
+ function engineWork() {
5681
+ return {
5682
+ applyPatch: applies,
5683
+ cloneBytes: clonedBytes
5684
+ };
5685
+ }
5686
+
5687
+ // #region the patch
5688
+
5689
+ /**
5690
+ * Applies one patch to a module's content. Nothing is mutated: the module
5691
+ * passed in is still what it was, and the one returned shares every subtree
5692
+ * the patch did not touch.
5693
+ *
5694
+ * `failed` means the patch does not apply, and then none of it does: half a
5695
+ * patch is half an edit. The caller stops the module's chain there.
5696
+ *
5697
+ * `schema` is the module's serialized schema: it says which ops reach into a
5698
+ * `.jsonValues()` entry. Without one, every op is an op on the source.
5699
+ */
5700
+ function applyPatchRecord(module, record, schema) {
5701
+ applies++;
5702
+ var patchId = record.patchId;
5703
+ var fail = function fail(message) {
5704
+ return {
5705
+ status: "failed",
5706
+ patchId: patchId,
5707
+ message: message
5708
+ };
5709
+ };
5710
+ var source = module.source;
5711
+ var entries = null;
5712
+ var stateOf = function stateOf(key) {
5713
+ var _get;
5714
+ return (_get = (entries !== null && entries !== void 0 ? entries : module.entries).get(key)) !== null && _get !== void 0 ? _get : implicitState(source, key);
5715
+ };
5716
+ var setEntry = function setEntry(key, state) {
5717
+ if (entries === null) {
5718
+ clonedBytes += SLOT_BYTES * module.entries.size;
5719
+ entries = new Map(module.entries);
5720
+ }
5721
+ entries.set(key, state);
5722
+ };
5723
+ /** Sets it, unless the entry already failed: an entry's error is sticky. */
5724
+ var settle = function settle(key, state) {
5725
+ var _stateOf;
5726
+ if (((_stateOf = stateOf(key)) === null || _stateOf === void 0 ? void 0 : _stateOf.kind) !== "error") setEntry(key, state);
5727
+ };
5728
+ /**
5729
+ * The record's keys as they stand while this patch is walked, for a write
5730
+ * of the whole record to expand against. Only the key set: the source holds
5731
+ * markers, and an entry's content may not be loaded. Seeded from the source
5732
+ * BEFORE this patch and kept current by the whole-entry ops below.
5733
+ */
5734
+ var keys = null;
5735
+ var currentKeys = function currentKeys() {
5736
+ if (keys === null) {
5737
+ keys = new Map();
5738
+ if (isObjectNode(module.source)) {
5739
+ for (var _i = 0, _Object$keys = Object.keys(module.source); _i < _Object$keys.length; _i++) {
5740
+ var _key = _Object$keys[_i];
5741
+ keys.set(_key, undefined);
5742
+ }
5743
+ }
5744
+ }
5745
+ return keys;
5746
+ };
5747
+ /** Where a file's `patch_id` lands in the source; last write wins its place. */
5748
+ var fileMarks = new Map();
5749
+ /** And in an entry's content. */
5750
+ var entryFileMarks = [];
5751
+ var _iterator = _createForOfIteratorHelper(record.patch),
5752
+ _step;
5753
+ try {
5754
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
5755
+ var rawOp = _step.value;
5756
+ /*
5757
+ * A write of the WHOLE record fans out into per-entry ops first, through
5758
+ * the expansion the commit flow uses too (`expandJsonValuesRootOp`).
5759
+ * Left as it is, it would put content where the source holds markers.
5760
+ */
5761
+ var ops = [rawOp];
5762
+ if (schema !== undefined && isJsonValuesRootOp(schema, rawOp)) {
5763
+ var expanded = expandJsonValuesRootOp(rawOp, currentKeys());
5764
+ if (result.isErr(expanded)) return fail(expanded.error.message);
5765
+ ops = expanded.value;
5766
+ }
5767
+ var _iterator3 = _createForOfIteratorHelper(ops),
5768
+ _step3;
5769
+ try {
5770
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
5771
+ var _op = _step3.value;
5772
+ var cls = schema === undefined ? {
5773
+ kind: "normal"
5774
+ } : classifyJsonValuesOp(schema, _op.path);
5775
+ if (_op.op === "file") {
5776
+ var _op$nestedFilePath;
5777
+ /*
5778
+ * A file op carries bytes, not content. What the content needs is
5779
+ * the `patch_id` saying those bytes are not published yet, so the
5780
+ * file is served from the patch rather than from a `/public` path
5781
+ * that holds nothing. Keyed by where it LANDS -- the field's path
5782
+ * plus `nestedFilePath` -- because a video's poster and captions are
5783
+ * file ops on the video's own path. A delete has no bytes to point at.
5784
+ */
5785
+ if (_op.value === null) continue;
5786
+ var nested = (_op$nestedFilePath = _op.nestedFilePath) !== null && _op$nestedFilePath !== void 0 ? _op$nestedFilePath : [];
5787
+ if (cls.kind === "entry" && cls.subPath.length > 0) {
5788
+ if (cls.recordPath.length === 0) {
5789
+ entryFileMarks.push({
5790
+ key: cls.entryKey,
5791
+ path: cls.subPath.concat(nested, "patch_id")
5792
+ });
5793
+ }
5794
+ continue;
5795
+ }
5796
+ var at = _op.path.concat(nested, "patch_id");
5797
+ fileMarks.set(JSON.stringify(at), at);
5798
+ if (cls.kind === "entry" && cls.recordPath.length === 0) {
5799
+ // The entry IS the media: its content gets the mark too.
5800
+ entryFileMarks.push({
5801
+ key: cls.entryKey,
5802
+ path: nested.concat("patch_id")
5803
+ });
5804
+ }
5805
+ continue;
5806
+ }
5807
+ if (cls.kind === "normal") {
5808
+ var _applied = applyOp(source, _op);
5809
+ if (result.isErr(_applied)) return fail(_applied.error.message);
5810
+ source = _applied.value;
5811
+ if (_op.op === "move") {
5812
+ var fromKey = wholeEntryKey(schema, _op.from);
5813
+ if (fromKey !== undefined) settle(fromKey, {
5814
+ kind: "deleted"
5815
+ });
5816
+ }
5817
+ if (_op.path.length === 0 && (schema === null || schema === void 0 ? void 0 : schema.type) === "record" && schema.jsonValues === true) {
5818
+ // The record itself was replaced (by something that is not a
5819
+ // record): every entry went with it.
5820
+ clonedBytes += SLOT_BYTES * (entries !== null && entries !== void 0 ? entries : module.entries).size;
5821
+ entries = new Map();
5822
+ }
5823
+ continue;
5824
+ }
5825
+ if (cls.recordPath.length > 0) {
5826
+ return fail("Nested .jsonValues() records are not supported: '".concat(cls.recordPath.join("."), "'. Use .jsonValues() only on a module's root record/router."));
5827
+ }
5828
+ var _key3 = cls.entryKey;
5829
+ if (cls.subPath.length > 0) {
5830
+ // An edit INSIDE an entry: its content, never the source's marker.
5831
+ if (_op.op === "move" || _op.op === "copy") {
5832
+ var fromCls = schema === undefined ? {
5833
+ kind: "normal"
5834
+ } : classifyJsonValuesOp(schema, _op.from);
5835
+ if (fromCls.kind !== "entry" || fromCls.entryKey !== _key3) {
5836
+ return fail("Cannot '".concat(_op.op, "' between different jsonValues entries"));
5837
+ }
5838
+ }
5839
+ var rebased = rebaseContentOp(_op, cls.recordPath.length + 1);
5840
+ if (result.isErr(rebased)) return fail(rebased.error.message);
5841
+ var _state = stateOf(_key3);
5842
+ if (_state === undefined || _state.kind === "deleted") {
5843
+ setEntry(_key3, {
5844
+ kind: "error",
5845
+ patchId: patchId,
5846
+ message: "Cannot edit jsonValues entry '".concat(_key3, "': it does not exist")
5847
+ });
5848
+ } else if (_state.kind === "waiting") {
5849
+ setEntry(_key3, {
5850
+ kind: "waiting",
5851
+ from: _state.from,
5852
+ ops: {
5853
+ op: rebased.value,
5854
+ patchId: patchId,
5855
+ before: _state.ops
5856
+ }
5857
+ });
5858
+ } else if (_state.kind === "content") {
5859
+ var _applied2 = applyOp(_state.content, rebased.value);
5860
+ setEntry(_key3, result.isErr(_applied2) ? {
5861
+ kind: "error",
5862
+ patchId: patchId,
5863
+ message: _applied2.error.message
5864
+ } : {
5865
+ kind: "content",
5866
+ content: _applied2.value
5867
+ });
5868
+ }
5869
+ continue;
5870
+ }
5871
+
5872
+ // A WHOLE entry: a marker in the source, content in the entry.
5873
+ var marker = _defineProperty(_defineProperty({}, VAL_EXTENSION, "json"), "patch_id", patchId);
5874
+ if (_op.op === "add" || _op.op === "replace") {
5875
+ var written = _op.op === "add" ? addAt(source, _op.path, marker) : replaceAt(source, _op.path, marker);
5876
+ if (result.isErr(written)) return fail(written.error.message);
5877
+ source = written.value;
5878
+ currentKeys().set(_key3, undefined);
5879
+ settle(_key3, {
5880
+ kind: "content",
5881
+ content: _op.value
5882
+ });
5883
+ } else if (_op.op === "remove") {
5884
+ var removed = removeAt(source, _op.path);
5885
+ if (result.isErr(removed)) return fail(removed.error.message);
5886
+ source = removed.value.doc;
5887
+ currentKeys()["delete"](_key3);
5888
+ settle(_key3, {
5889
+ kind: "deleted"
5890
+ });
5891
+ } else if (_op.op === "move" || _op.op === "copy") {
5892
+ var _fromCls = schema === undefined ? {
5893
+ kind: "normal"
5894
+ } : classifyJsonValuesOp(schema, _op.from);
5895
+ if (_fromCls.kind !== "entry" || _fromCls.subPath.length !== 0 || _fromCls.recordPath.length !== 0) {
5896
+ return fail("Cannot '".concat(_op.op, "' a jsonValues entry across records or from a non-entry path (from '").concat(_op.from.join("."), "' to '").concat(_op.path.join("."), "')"));
5897
+ }
5898
+ var _fromKey = _fromCls.entryKey;
5899
+ // Read before anything moves: the destination is the source entry
5900
+ // as it stood at this op.
5901
+ var fromState = stateOf(_fromKey);
5902
+ var added = addAt(source, _op.path, marker);
5903
+ if (result.isErr(added)) return fail(added.error.message);
5904
+ source = added.value;
5905
+ currentKeys().set(_key3, undefined);
5906
+ if (_op.op === "move") {
5907
+ var _removed = removeAt(source, _op.from);
5908
+ if (result.isErr(_removed)) return fail(_removed.error.message);
5909
+ source = _removed.value.doc;
5910
+ currentKeys()["delete"](_fromKey);
5911
+ }
5912
+ settle(_key3, fromState === undefined || fromState.kind === "deleted" ? {
5913
+ kind: "error",
5914
+ patchId: patchId,
5915
+ message: "Cannot resolve '".concat(_op.op, "' of jsonValues entry '").concat(_key3, "' from its own content")
5916
+ } : fromState);
5917
+ if (_op.op === "move" && _fromKey !== _key3) {
5918
+ settle(_fromKey, {
5919
+ kind: "deleted"
5920
+ });
5921
+ }
5922
+ } else {
5923
+ // `test` of a whole entry: the commit refuses it (`ValOps.prepare`),
5924
+ // so the read does too, rather than showing a patch publishing fails.
5925
+ return fail("Unsupported op '".concat(_op.op, "' on jsonValues entry '").concat(_key3, "' (supported: add, remove, replace, move, copy)"));
5926
+ }
5927
+ }
5928
+ } catch (err) {
5929
+ _iterator3.e(err);
5930
+ } finally {
5931
+ _iterator3.f();
5932
+ }
5933
+ }
5934
+ } catch (err) {
5935
+ _iterator.e(err);
5936
+ } finally {
5937
+ _iterator.f();
5938
+ }
5939
+ var _iterator2 = _createForOfIteratorHelper(fileMarks.values()),
5940
+ _step2;
5941
+ try {
5942
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
5943
+ var _at = _step2.value;
5944
+ var marked = addAt(source, _at, patchId);
5945
+ if (result.isErr(marked)) return fail(marked.error.message);
5946
+ source = marked.value;
5947
+ }
5948
+ } catch (err) {
5949
+ _iterator2.e(err);
5950
+ } finally {
5951
+ _iterator2.f();
5952
+ }
5953
+ for (var _i2 = 0, _entryFileMarks = entryFileMarks; _i2 < _entryFileMarks.length; _i2++) {
5954
+ var _entryFileMarks$_i = _entryFileMarks[_i2],
5955
+ _key2 = _entryFileMarks$_i.key,
5956
+ path = _entryFileMarks$_i.path;
5957
+ var state = stateOf(_key2);
5958
+ var op = {
5959
+ op: "add",
5960
+ path: path,
5961
+ value: patchId
5962
+ };
5963
+ if ((state === null || state === void 0 ? void 0 : state.kind) === "content") {
5964
+ var applied = applyOp(state.content, op);
5965
+ setEntry(_key2, result.isErr(applied) ? {
5966
+ kind: "error",
5967
+ patchId: patchId,
5968
+ message: applied.error.message
5969
+ } : {
5970
+ kind: "content",
5971
+ content: applied.value
5972
+ });
5973
+ } else if ((state === null || state === void 0 ? void 0 : state.kind) === "waiting") {
5974
+ setEntry(_key2, {
5975
+ kind: "waiting",
5976
+ from: state.from,
5977
+ ops: {
5978
+ op: op,
5979
+ patchId: patchId,
5980
+ before: state.ops
5981
+ }
5982
+ });
5983
+ }
5984
+ }
5985
+ return {
5986
+ status: "applied",
5987
+ module: {
5988
+ source: source,
5989
+ entries: entries !== null && entries !== void 0 ? entries : module.entries
5990
+ }
5991
+ };
5992
+ }
5993
+
5994
+ /**
5995
+ * A module's chain: every patch in order, stopping at the first that does
5996
+ * not apply. `applied` is how many did, so the chain the content is AT is
5997
+ * the first `applied` ids -- what its version names.
5998
+ */
5999
+ function applyChain(module, records, schema) {
6000
+ var current = module;
6001
+ var _iterator4 = _createForOfIteratorHelper(records.entries()),
6002
+ _step4;
6003
+ try {
6004
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
6005
+ var _step4$value = _slicedToArray(_step4.value, 2),
6006
+ index = _step4$value[0],
6007
+ record = _step4$value[1];
6008
+ var res = applyPatchRecord(current, record, schema);
6009
+ if (res.status === "failed") {
6010
+ return {
6011
+ module: current,
6012
+ applied: index,
6013
+ failed: {
6014
+ patchId: res.patchId,
6015
+ message: res.message
6016
+ }
6017
+ };
6018
+ }
6019
+ current = res.module;
6020
+ }
6021
+ } catch (err) {
6022
+ _iterator4.e(err);
6023
+ } finally {
6024
+ _iterator4.f();
6025
+ }
6026
+ return {
6027
+ module: current,
6028
+ applied: records.length,
6029
+ failed: null
6030
+ };
6031
+ }
6032
+
6033
+ // #region entries
6034
+
6035
+ /**
6036
+ * What an entry is in this module: what the chain made it, or -- for one the
6037
+ * chain never touched -- the base's, if the source has its marker.
6038
+ */
6039
+ function entryState(module, key) {
6040
+ var _module$entries$get;
6041
+ return (_module$entries$get = module.entries.get(key)) !== null && _module$entries$get !== void 0 ? _module$entries$get : implicitState(module.source, key);
6042
+ }
6043
+ function implicitState(source, key) {
6044
+ return isObjectNode(source) && Object.prototype.hasOwnProperty.call(source, key) && Internal.isJson(source[key]) ? {
6045
+ kind: "waiting",
6046
+ from: key,
6047
+ ops: null
6048
+ } : undefined;
6049
+ }
6050
+
6051
+ /** An entry's content, read. */
6052
+
6053
+ /**
6054
+ * An entry's content: what {@link entryState} says, with what it was waiting
6055
+ * for applied. `baseContentOf` is the BASE content of an entry by key (its
6056
+ * `*.val.json`, or what a save left there), `undefined` for one the base does
6057
+ * not have.
6058
+ */
6059
+ function resolveEntry(key, state, baseContentOf) {
6060
+ if (state === undefined || state.kind === "deleted") {
6061
+ return {
6062
+ kind: "missing"
6063
+ };
6064
+ }
6065
+ if (state.kind === "error") {
6066
+ return {
6067
+ kind: "error",
6068
+ message: state.message,
6069
+ patchId: state.patchId
6070
+ };
6071
+ }
6072
+ if (state.kind === "content") {
6073
+ return {
6074
+ kind: "content",
6075
+ content: state.content
6076
+ };
6077
+ }
6078
+ var held = [];
6079
+ for (var at = state.ops; at !== null; at = at.before) held.push(at);
6080
+ var content = baseContentOf(state.from);
6081
+ if (content === undefined) {
6082
+ var first = held[held.length - 1];
6083
+ return first === undefined ? {
6084
+ kind: "missing"
6085
+ } : {
6086
+ kind: "error",
6087
+ message: "Cannot edit jsonValues entry '".concat(key, "': it does not exist"),
6088
+ patchId: first.patchId
6089
+ };
6090
+ }
6091
+ for (var i = held.length - 1; i >= 0; i--) {
6092
+ var _held$i = held[i],
6093
+ op = _held$i.op,
6094
+ patchId = _held$i.patchId;
6095
+ var applied = applyOp(content, op);
6096
+ if (result.isErr(applied)) {
6097
+ return {
6098
+ kind: "error",
6099
+ message: applied.error.message,
6100
+ patchId: patchId
6101
+ };
6102
+ }
6103
+ content = applied.value;
6104
+ }
6105
+ return {
6106
+ kind: "content",
6107
+ content: content
6108
+ };
6109
+ }
6110
+
6111
+ // #region copy-on-write JSON ops
6112
+
6113
+ /*
6114
+ * RFC 6902 as `JSONOps` (in `@valbuild/core/patch`) applies it -- the same
6115
+ * results and the same error messages, which the conformance cases hold --
6116
+ * but without mutating anything: a container on an op's path is copied, and
6117
+ * everything else is shared.
6118
+ */
6119
+
6120
+ function isArrayNode(node) {
6121
+ return Array.isArray(node);
6122
+ }
6123
+ function isObjectNode(node) {
6124
+ return _typeof(node) === "object" && node !== null && !Array.isArray(node);
6125
+ }
6126
+ function hasOwn(node, key) {
6127
+ return Object.prototype.hasOwnProperty.call(node, key);
6128
+ }
6129
+ function patchError(message) {
6130
+ return result.err(new PatchError(message));
6131
+ }
6132
+ function copyArray(node) {
6133
+ clonedBytes += SLOT_BYTES * node.length;
6134
+ return node.slice();
6135
+ }
6136
+
6137
+ /**
6138
+ * A shallow copy, key by key. Not a spread, which was tried: a module's root
6139
+ * is a large object in dictionary mode, and V8 spreads one of forty thousand
6140
+ * keys at about half this speed (the 5 MB keystroke of
6141
+ * `scenarios/measure/keystroke.ts` went from 19 ms to 33).
6142
+ */
6143
+ function copyObject(node) {
6144
+ var copy = {};
6145
+ var slots = 0;
6146
+ for (var _i3 = 0, _Object$keys2 = Object.keys(node); _i3 < _Object$keys2.length; _i3++) {
6147
+ var _key4 = _Object$keys2[_i3];
6148
+ setOwn(copy, _key4, node[_key4]);
6149
+ slots++;
6150
+ }
6151
+ clonedBytes += SLOT_BYTES * slots;
6152
+ return copy;
6153
+ }
6154
+
6155
+ /** An own property, even for `__proto__`, which `=` would not create. */
6156
+ function setOwn(target, key, value) {
6157
+ if (key === "__proto__") {
6158
+ Object.defineProperty(target, key, {
6159
+ value: value,
6160
+ writable: true,
6161
+ enumerable: true,
6162
+ configurable: true
6163
+ });
6164
+ } else {
6165
+ target[key] = value;
6166
+ }
6167
+ }
6168
+ function arrayIndex(key, node, inclusive) {
6169
+ if (inclusive && key === "-") return result.ok(node.length);
6170
+ var index = parseAndValidateArrayIndex(key);
6171
+ if (result.isErr(index)) return index;
6172
+ return (inclusive ? index.value <= node.length : index.value < node.length) ? index : patchError("Array index out of bounds");
6173
+ }
6174
+ function childOf(node, key) {
6175
+ if (isArrayNode(node)) {
6176
+ var index = parseAndValidateArrayIndex(key);
6177
+ if (result.isErr(index)) return index;
6178
+ return index.value >= node.length ? patchError("Array index out of bounds") : result.ok(node[index.value]);
6179
+ }
6180
+ if (isObjectNode(node)) {
6181
+ return result.ok(hasOwn(node, key) ? node[key] : undefined);
6182
+ }
6183
+ return patchError("Cannot access non-object/array");
6184
+ }
6185
+
6186
+ /** `node` with `key` set to `value`: the one copy a step on a path costs. */
6187
+ function withChild(node, key, value) {
6188
+ if (isArrayNode(node)) {
6189
+ var copy = copyArray(node);
6190
+ copy[Number(key)] = value;
6191
+ return copy;
6192
+ }
6193
+ if (isObjectNode(node)) {
6194
+ var _copy = copyObject(node);
6195
+ setOwn(_copy, key, value);
6196
+ return _copy;
6197
+ }
6198
+ return node;
6199
+ }
6200
+
6201
+ /**
6202
+ * Walks `path` to the parent of its last segment, lets `leaf` change that
6203
+ * parent, and copies every container on the way back up. Errors as
6204
+ * `JSONOps`'s pointer walk: the first missing step is reported.
6205
+ */
6206
+ function updateAt(node, path, index, leaf) {
6207
+ var key = path[index];
6208
+ if (index === path.length - 1) return leaf(node, key);
6209
+ var child = childOf(node, key);
6210
+ if (result.isErr(child)) return child;
6211
+ if (child.value === undefined) {
6212
+ return patchError("Path refers to non-existing object/array");
6213
+ }
6214
+ var updated = updateAt(child.value, path, index + 1, leaf);
6215
+ if (result.isErr(updated)) return updated;
6216
+ return result.ok({
6217
+ node: updated.value.node === child.value ? node : withChild(node, key, updated.value.node),
6218
+ out: updated.value.out
6219
+ });
6220
+ }
6221
+ function isNonEmpty(path) {
6222
+ return path.length > 0;
6223
+ }
6224
+ function getAt(doc, path) {
6225
+ var node = doc;
6226
+ var _iterator5 = _createForOfIteratorHelper(path),
6227
+ _step5;
6228
+ try {
6229
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
6230
+ var _key5 = _step5.value;
6231
+ var child = childOf(node, _key5);
6232
+ if (result.isErr(child)) return child;
6233
+ if (child.value === undefined) {
6234
+ return patchError("Path refers to non-existing object/array");
6235
+ }
6236
+ node = child.value;
6237
+ }
6238
+ } catch (err) {
6239
+ _iterator5.e(err);
6240
+ } finally {
6241
+ _iterator5.f();
6242
+ }
6243
+ return result.ok(node);
6244
+ }
6245
+ function addAt(doc, path, value) {
6246
+ if (!isNonEmpty(path)) return result.ok(value);
6247
+ var res = updateAt(doc, path, 0, function (parent, key) {
6248
+ if (isArrayNode(parent)) {
6249
+ var index = arrayIndex(key, parent, true);
6250
+ if (result.isErr(index)) return index;
6251
+ var copy = copyArray(parent);
6252
+ copy.splice(index.value, 0, value);
6253
+ return result.ok({
6254
+ node: copy,
6255
+ out: null
6256
+ });
6257
+ }
6258
+ if (isObjectNode(parent)) {
6259
+ var _copy2 = copyObject(parent);
6260
+ setOwn(_copy2, key, value);
6261
+ return result.ok({
6262
+ node: _copy2,
6263
+ out: null
6264
+ });
6265
+ }
6266
+ return patchError("Cannot add to non-object/array");
6267
+ });
6268
+ return result.isErr(res) ? res : result.ok(res.value.node);
6269
+ }
6270
+ function replaceAt(doc, path, value) {
6271
+ if (!isNonEmpty(path)) return result.ok(value);
6272
+ var res = updateAt(doc, path, 0, function (parent, key) {
6273
+ if (isArrayNode(parent)) {
6274
+ var index = arrayIndex(key, parent, false);
6275
+ if (result.isErr(index)) return index;
6276
+ var copy = copyArray(parent);
6277
+ copy[index.value] = value;
6278
+ return result.ok({
6279
+ node: copy,
6280
+ out: null
6281
+ });
6282
+ }
6283
+ if (isObjectNode(parent)) {
6284
+ if (!hasOwn(parent, key)) {
6285
+ return patchError("Cannot replace object element which does not exist");
6286
+ }
6287
+ var _copy3 = copyObject(parent);
6288
+ setOwn(_copy3, key, value);
6289
+ return result.ok({
6290
+ node: _copy3,
6291
+ out: null
6292
+ });
6293
+ }
6294
+ return patchError("Cannot replace in non-object/array");
6295
+ });
6296
+ return result.isErr(res) ? res : result.ok(res.value.node);
6297
+ }
6298
+ function removeAt(doc, path) {
6299
+ if (!isNonEmpty(path)) {
6300
+ return patchError("Cannot remove from non-object/array");
6301
+ }
6302
+ var res = updateAt(doc, path, 0, function (parent, key) {
6303
+ if (isArrayNode(parent)) {
6304
+ var index = arrayIndex(key, parent, false);
6305
+ if (result.isErr(index)) return index;
6306
+ var copy = copyArray(parent);
6307
+ var _copy$splice = copy.splice(index.value, 1),
6308
+ _copy$splice2 = _slicedToArray(_copy$splice, 1),
6309
+ removed = _copy$splice2[0];
6310
+ return result.ok({
6311
+ node: copy,
6312
+ out: removed
6313
+ });
6314
+ }
6315
+ if (isObjectNode(parent) && hasOwn(parent, key)) {
6316
+ var _copy4 = copyObject(parent);
6317
+ var _removed2 = parent[key];
6318
+ delete _copy4[key];
6319
+ return result.ok({
6320
+ node: _copy4,
6321
+ out: _removed2
6322
+ });
6323
+ }
6324
+ return patchError("Cannot remove from non-object/array");
6325
+ });
6326
+ return result.isErr(res) ? res : result.ok({
6327
+ doc: res.value.node,
6328
+ removed: res.value.out
6329
+ });
6330
+ }
6331
+
6332
+ /** One op on a document. `file` is not an op on content: the caller marks. */
6333
+ function applyOp(doc, op) {
6334
+ switch (op.op) {
6335
+ case "add":
6336
+ return addAt(doc, op.path, op.value);
6337
+ case "replace":
6338
+ return replaceAt(doc, op.path, op.value);
6339
+ case "remove":
6340
+ {
6341
+ var removed = removeAt(doc, op.path);
6342
+ return result.isErr(removed) ? removed : result.ok(removed.value.doc);
6343
+ }
6344
+ case "move":
6345
+ {
6346
+ var _removed3 = removeAt(doc, op.from);
6347
+ if (result.isErr(_removed3)) return _removed3;
6348
+ return addAt(_removed3.value.doc, op.path, _removed3.value.removed);
6349
+ }
6350
+ case "copy":
6351
+ {
6352
+ // Shared, not cloned: nothing here mutates, so two places can hold one
6353
+ // value.
6354
+ var value = getAt(doc, op.from);
6355
+ if (result.isErr(value)) return value;
6356
+ return addAt(doc, op.path, value.value);
6357
+ }
6358
+ case "test":
6359
+ {
6360
+ // As `applyPatch` in `@valbuild/core/patch`, which the commit applies
6361
+ // `.val.ts` text through: a path that does not exist, or a value there
6362
+ // that is not deeply equal, fails the patch (RFC 6902, 4.6). The two
6363
+ // have to agree, or a draft shows a patch Publish refuses.
6364
+ var _value = getAt(doc, op.path);
6365
+ if (result.isErr(_value)) return _value;
6366
+ if (!deepEqual(op.value, _value.value)) return patchError("Test failed");
6367
+ return result.ok(doc);
6368
+ }
6369
+ case "file":
6370
+ return patchError("Cannot apply a file patch here");
6371
+ }
6372
+ }
6373
+
6374
+ /*
6375
+ * The store: modules by address. See `docs/plans/one-content-model.md`,
6376
+ * "The store: one address, immutable values, tiers".
6377
+ *
6378
+ * Content at a version never changes, so a store of modules by `moduleKey`
6379
+ * needs no invalidation, only eviction. A base is a version with the empty
6380
+ * chain, so the same store holds both. Every place that keeps modules uses
6381
+ * the same tiers, in the order that fits it: Val's server keeps
6382
+ * `[memoryTier, bundleTier, …]` in front of its head (`ValHead` in
6383
+ * `@valbuild/server`), and the Studio's will put an origin tier behind them
6384
+ * (phase 7).
6385
+ */
6386
+
6387
+ /** One module's content at one version, as a tier holds it. */
6388
+
6389
+ // #region counted work
6390
+
6391
+ var tierHits = new Map();
6392
+ var tierMisses = 0;
6393
+
6394
+ /**
6395
+ * What the stores in this process have answered, by tier, and how often
6396
+ * none of them had a key: a test takes it before and after. Never reset.
6397
+ */
6398
+ function storeWork() {
6399
+ return {
6400
+ hits: Object.fromEntries(tierHits),
6401
+ misses: tierMisses
6402
+ };
6403
+ }
6404
+
6405
+ // #region tiered
6406
+
6407
+ /**
6408
+ * Asks each tier in turn; what a slower tier answers is written to the faster
6409
+ * ones before it. A tier that answers `null` has no say.
6410
+ */
6411
+ function tiered(tiers) {
6412
+ return {
6413
+ name: tiers.map(function (tier) {
6414
+ return tier.name;
6415
+ }).join(">"),
6416
+ get: function get(key) {
6417
+ return _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
6418
+ var _iterator, _step, _tierHits$get, _step$value, index, tier, found, _iterator2, _step2, _faster$put, faster, _t;
6419
+ return _regenerator().w(function (_context) {
6420
+ while (1) switch (_context.p = _context.n) {
6421
+ case 0:
6422
+ _iterator = _createForOfIteratorHelper(tiers.entries());
6423
+ _context.p = 1;
6424
+ _iterator.s();
6425
+ case 2:
6426
+ if ((_step = _iterator.n()).done) {
6427
+ _context.n = 6;
6428
+ break;
6429
+ }
6430
+ _step$value = _slicedToArray(_step.value, 2), index = _step$value[0], tier = _step$value[1];
6431
+ _context.n = 3;
6432
+ return tier.get(key);
6433
+ case 3:
6434
+ found = _context.v;
6435
+ if (!(found === null)) {
6436
+ _context.n = 4;
6437
+ break;
6438
+ }
6439
+ return _context.a(3, 5);
6440
+ case 4:
6441
+ tierHits.set(tier.name, ((_tierHits$get = tierHits.get(tier.name)) !== null && _tierHits$get !== void 0 ? _tierHits$get : 0) + 1);
6442
+ _iterator2 = _createForOfIteratorHelper(tiers.slice(0, index));
6443
+ try {
6444
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
6445
+ faster = _step2.value;
6446
+ (_faster$put = faster.put) === null || _faster$put === void 0 || _faster$put.call(faster, key, found, found.bytes);
6447
+ }
6448
+ } catch (err) {
6449
+ _iterator2.e(err);
6450
+ } finally {
6451
+ _iterator2.f();
6452
+ }
6453
+ return _context.a(2, found);
6454
+ case 5:
6455
+ _context.n = 2;
6456
+ break;
6457
+ case 6:
6458
+ _context.n = 8;
6459
+ break;
6460
+ case 7:
6461
+ _context.p = 7;
6462
+ _t = _context.v;
6463
+ _iterator.e(_t);
6464
+ case 8:
6465
+ _context.p = 8;
6466
+ _iterator.f();
6467
+ return _context.f(8);
6468
+ case 9:
6469
+ tierMisses++;
6470
+ return _context.a(2, null);
6471
+ }
6472
+ }, _callee, null, [[1, 7, 8, 9]]);
6473
+ }))();
6474
+ },
6475
+ put: function put(key, value, bytes) {
6476
+ var _tiers$, _tiers$$put;
6477
+ (_tiers$ = tiers[0]) === null || _tiers$ === void 0 || (_tiers$$put = _tiers$.put) === null || _tiers$$put === void 0 || _tiers$$put.call(_tiers$, key, value, bytes);
6478
+ }
6479
+ };
6480
+ }
6481
+
6482
+ // #region memory
6483
+
6484
+ /**
6485
+ * Bounded by bytes; keeps the last `lastPerModule` versions per module path
6486
+ * (and, apart from them, the last `lastPerModule` bases), and lets go of the least recently read value when over `budgetBytes`. A
6487
+ * value bigger than the whole budget is still held while it is the newest,
6488
+ * so the read that made it is answered; the next put lets it go.
6489
+ */
6490
+ function memoryTier(opts) {
6491
+ var _opts$name;
6492
+ /** In least-recently-read order: a read moves a key to the end. */
6493
+ var held = new Map();
6494
+ /** Per module, its keys oldest first. */
6495
+ var rings = new Map();
6496
+ var bytes = 0;
6497
+ var evicted = 0;
6498
+ var versionOf = function versionOf(key) {
6499
+ var _held$get;
6500
+ return (_held$get = held.get(key)) === null || _held$get === void 0 ? void 0 : _held$get.value.version;
6501
+ };
6502
+ var drop = function drop(key) {
6503
+ var entry = held.get(key);
6504
+ if (entry === undefined) return;
6505
+ held["delete"](key);
6506
+ bytes -= entry.bytes;
6507
+ evicted++;
6508
+ var ring = rings.get(entry.value.path);
6509
+ if (ring !== undefined) {
6510
+ var at = ring.indexOf(key);
6511
+ if (at >= 0) ring.splice(at, 1);
6512
+ if (ring.length === 0) rings["delete"](entry.value.path);
6513
+ }
6514
+ };
6515
+ return {
6516
+ name: (_opts$name = opts.name) !== null && _opts$name !== void 0 ? _opts$name : "memory",
6517
+ get: function get(key) {
6518
+ var entry = held.get(key);
6519
+ if (entry === undefined) return null;
6520
+ held["delete"](key);
6521
+ held.set(key, entry);
6522
+ return entry.value;
6523
+ },
6524
+ put: function put(key, value, valueBytes) {
6525
+ var _rings$get;
6526
+ if (held.has(key)) {
6527
+ drop(key);
6528
+ evicted--;
6529
+ }
6530
+ held.set(key, {
6531
+ value: value,
6532
+ bytes: valueBytes
6533
+ });
6534
+ bytes += valueBytes;
6535
+ var ring = (_rings$get = rings.get(value.path)) !== null && _rings$get !== void 0 ? _rings$get : [];
6536
+ ring.push(key);
6537
+ rings.set(value.path, ring);
6538
+ // The last N versions, and apart from them the last N bases: a base
6539
+ // is what a slower tier answered (the bundle's own object, held
6540
+ // anyway) and written up here, and it must not push out the version
6541
+ // before the newest -- which a reader of the previous version still
6542
+ // asks for.
6543
+ var isBase = value.version.chain === EMPTY_CHAIN;
6544
+ var sameKind = ring.filter(function (held) {
6545
+ var _versionOf;
6546
+ return ((_versionOf = versionOf(held)) === null || _versionOf === void 0 ? void 0 : _versionOf.chain) === EMPTY_CHAIN === isBase;
6547
+ });
6548
+ var _iterator3 = _createForOfIteratorHelper(sameKind.slice(0, Math.max(0, sameKind.length - opts.lastPerModule))),
6549
+ _step3;
6550
+ try {
6551
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
6552
+ var oldest = _step3.value;
6553
+ drop(oldest);
6554
+ }
6555
+ } catch (err) {
6556
+ _iterator3.e(err);
6557
+ } finally {
6558
+ _iterator3.f();
6559
+ }
6560
+ var _iterator4 = _createForOfIteratorHelper(held.keys()),
6561
+ _step4;
6562
+ try {
6563
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
6564
+ var lru = _step4.value;
6565
+ if (bytes <= opts.budgetBytes) break;
6566
+ if (lru === key) continue;
6567
+ drop(lru);
6568
+ }
6569
+ } catch (err) {
6570
+ _iterator4.e(err);
6571
+ } finally {
6572
+ _iterator4.f();
6573
+ }
6574
+ },
6575
+ charge: function charge(key, extra) {
6576
+ var entry = held.get(key);
6577
+ if (entry === undefined) return;
6578
+ entry.bytes += extra;
6579
+ bytes += extra;
6580
+ },
6581
+ latest: function latest(path) {
6582
+ var _held$get$value, _held$get2;
6583
+ var ring = rings.get(path);
6584
+ var newest = ring === null || ring === void 0 ? void 0 : ring[ring.length - 1];
6585
+ return newest === undefined ? null : (_held$get$value = (_held$get2 = held.get(newest)) === null || _held$get2 === void 0 ? void 0 : _held$get2.value) !== null && _held$get$value !== void 0 ? _held$get$value : null;
6586
+ },
6587
+ stats: function stats() {
6588
+ // Versions: what a chain made. A base written up from a slower tier is
6589
+ // the build's own object (counted at no bytes), held beside them, and
6590
+ // not one of the module's versions.
6591
+ var mostVersions = 0;
6592
+ var mostBytes = 0;
6593
+ var versions = 0;
6594
+ var modules = 0;
6595
+ var _iterator5 = _createForOfIteratorHelper(rings.values()),
6596
+ _step5;
6597
+ try {
6598
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
6599
+ var ring = _step5.value;
6600
+ var made = ring.filter(function (key) {
6601
+ var _versionOf2;
6602
+ return ((_versionOf2 = versionOf(key)) === null || _versionOf2 === void 0 ? void 0 : _versionOf2.chain) !== EMPTY_CHAIN;
6603
+ });
6604
+ versions += made.length;
6605
+ if (made.length > 0) modules++;
6606
+ mostVersions = Math.max(mostVersions, made.length);
6607
+ var moduleBytes = 0;
6608
+ var _iterator6 = _createForOfIteratorHelper(ring),
6609
+ _step6;
6610
+ try {
6611
+ for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
6612
+ var _held$get$bytes, _held$get3;
6613
+ var _key = _step6.value;
6614
+ moduleBytes += (_held$get$bytes = (_held$get3 = held.get(_key)) === null || _held$get3 === void 0 ? void 0 : _held$get3.bytes) !== null && _held$get$bytes !== void 0 ? _held$get$bytes : 0;
6615
+ }
6616
+ } catch (err) {
6617
+ _iterator6.e(err);
6618
+ } finally {
6619
+ _iterator6.f();
6620
+ }
6621
+ mostBytes = Math.max(mostBytes, moduleBytes);
6622
+ }
6623
+ } catch (err) {
6624
+ _iterator5.e(err);
6625
+ } finally {
6626
+ _iterator5.f();
6627
+ }
6628
+ return {
6629
+ bytes: bytes,
6630
+ versions: versions,
6631
+ modules: modules,
6632
+ mostVersionsOfOneModule: mostVersions,
6633
+ mostBytesOfOneModule: mostBytes,
6634
+ evicted: evicted
6635
+ };
6636
+ }
6637
+ };
6638
+ }
6639
+
6640
+ // #region bundle
6641
+
6642
+ /**
6643
+ * The build's own modules, free: answers only a key whose version is the
6644
+ * bundle's -- its schema, its base and the empty chain -- with the bundle's
6645
+ * own object, which the process holds for every visitor anyway. `bundle`
6646
+ * says what the build evaluated each module to; `null` for a module the
6647
+ * build does not have.
6648
+ */
6649
+ function bundleTier(bundle) {
6650
+ var name = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "bundle";
6651
+ return {
6652
+ name: name,
6653
+ get: function get(key) {
6654
+ return _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
6655
+ var path, own, version;
6656
+ return _regenerator().w(function (_context2) {
6657
+ while (1) switch (_context2.n) {
6658
+ case 0:
6659
+ path = pathOfModuleKey(key);
6660
+ if (!(path === null)) {
6661
+ _context2.n = 1;
6662
+ break;
6663
+ }
6664
+ return _context2.a(2, null);
6665
+ case 1:
6666
+ _context2.n = 2;
6667
+ return bundle(path);
6668
+ case 2:
6669
+ own = _context2.v;
6670
+ if (!(own === null)) {
6671
+ _context2.n = 3;
6672
+ break;
6673
+ }
6674
+ return _context2.a(2, null);
6675
+ case 3:
6676
+ version = _objectSpread2(_objectSpread2({}, own.version), {}, {
6677
+ chain: EMPTY_CHAIN
6678
+ });
6679
+ if (!(moduleKey(path, version) !== key)) {
6680
+ _context2.n = 4;
6681
+ break;
6682
+ }
6683
+ return _context2.a(2, null);
6684
+ case 4:
6685
+ return _context2.a(2, {
6686
+ path: path,
6687
+ version: version,
6688
+ content: baseModuleContent(own.source),
6689
+ patches: [],
6690
+ bytes: 0
6691
+ });
6692
+ }
6693
+ }, _callee2);
6694
+ }))();
6695
+ }
6696
+ };
6697
+ }
6698
+
6699
+ /** The module a {@link moduleKey} is the address of, or `null` for none. */
6700
+ function pathOfModuleKey(key) {
6701
+ var at = key.lastIndexOf("@");
6702
+ return at <= 0 ? null : key.slice(0, at);
6703
+ }
6704
+
6705
+ /**
6706
+ * The version a {@link moduleKey} names, or `null` when it is not one. The
6707
+ * three parts are hex or empty, so the split is unambiguous.
6708
+ */
6709
+ function parseModuleKey(key) {
6710
+ var path = pathOfModuleKey(key);
6711
+ if (path === null) return null;
6712
+ var parts = key.slice(path.length + 1).split(".");
6713
+ var _parts = _slicedToArray(parts, 3),
6714
+ schema = _parts[0],
6715
+ base = _parts[1],
6716
+ chain = _parts[2];
6717
+ if (parts.length !== 3 || schema === undefined || base === undefined || chain === undefined) {
6718
+ return null;
6719
+ }
6720
+ return {
6721
+ path: path,
6722
+ version: {
6723
+ schema: schema,
6724
+ base: base,
6725
+ chain: chain
6726
+ }
6727
+ };
6728
+ }
6729
+
6730
+ // #region entries
6731
+
6732
+ /**
6733
+ * A `.jsonValues()` entry's version: the record's schema and base, the
6734
+ * entry's key, and the chain of the record's patches that touched THIS
6735
+ * entry -- so an entry no patch touched keeps its version while the record's
6736
+ * chain grows, and two reads at one entry version are the same content.
6737
+ * Entries are stored on their own, so a large record is not copied because
6738
+ * one entry changed.
6739
+ */
6740
+
6741
+ /** The address of an entry's content. Never changes meaning. */
6742
+ function entryAddress(path, v) {
6743
+ return "".concat(moduleKey(path, v), "#").concat(v.key);
6744
+ }
6745
+
6746
+ /** Whether two entry versions name the same content. */
6747
+ function sameEntryVersion(a, b) {
6748
+ return a.key === b.key && a.schema === b.schema && a.base === b.base && a.chain === b.chain;
6749
+ }
6750
+
6751
+ var SharedValConfig = z.object({
6752
+ contentHostUrl: z.string().optional(),
6753
+ appHostUrl: z.string().optional(),
6754
+ project: z.string().optional(),
6755
+ root: z.string().optional(),
6756
+ gitCommit: z.string().optional(),
6757
+ gitBranch: z.string().optional(),
6758
+ defaultTheme: z["enum"](["dark", "light"]).optional(),
6759
+ ai: z.object({
6760
+ commitMessages: z.object({
6761
+ disabled: z["boolean"]().optional()
6762
+ }).optional()
6763
+ }).optional()
6764
+ });
6765
+
6766
+ /**
6767
+ * Config that is shared between the client and server.
4766
6768
  */
4767
6769
 
4768
6770
  /**
@@ -7962,7 +9964,9 @@ function createSearchIndex() {
7962
9964
  tokenize: "forward"
7963
9965
  }),
7964
9966
  pathToLabel: new Map(),
7965
- docsByModule: new Map()
9967
+ docsByModule: new Map(),
9968
+ walkedByModule: new Map(),
9969
+ entriesByModule: new Map()
7966
9970
  };
7967
9971
  }
7968
9972
 
@@ -7976,77 +9980,230 @@ function createSearchIndex() {
7976
9980
  * `.jsonValues()` entries are indexed only once their content is loaded — an
7977
9981
  * un-loaded entry is an opaque marker that {@link traverseSchemaSource} skips, so
7978
9982
  * the index is naturally PARTIAL and grows as batches land. That is why the search
7979
- * UI loads the entries on the first query and re-indexes (debounced) as they
7980
- * arrive, rather than pretending an empty result set is an answer.
9983
+ * UI loads the entries on the first query and re-indexes as they arrive, rather
9984
+ * than pretending an empty result set is an answer -- and why a module whose
9985
+ * root is a record is re-indexed by entry ({@link indexRecordModule}): a pass
9986
+ * per batch walks the batch, not the record.
7981
9987
  */
7982
- function indexModule(searchIndex, moduleFilePath, source, schema) {
7983
- var index = searchIndex.index,
7984
- pathToLabel = searchIndex.pathToLabel,
7985
- docsByModule = searchIndex.docsByModule;
9988
+ function indexModule(searchIndex, moduleFilePath, source, schema,
9989
+ /**
9990
+ * Stop by this time (`performance.now()`), for a caller that must not hold
9991
+ * the thread: answers `false` when it stopped with entries of a record
9992
+ * module still to walk, and the same call again goes on from there. A
9993
+ * module that is not a record is walked whole, whatever the time.
9994
+ */
9995
+ until) {
9996
+ var walked = searchIndex.walkedByModule.get(moduleFilePath);
9997
+ if (walked !== undefined && walked.source === source && walked.schema === schemaKeyOf(schema)) {
9998
+ return true;
9999
+ }
10000
+ if (schema.type === "record" && isJsonRecord(source)) {
10001
+ var done = indexRecordModule(searchIndex, moduleFilePath, source, schema, until);
10002
+ if (done) {
10003
+ searchIndex.walkedByModule.set(moduleFilePath, {
10004
+ source: source,
10005
+ schema: schemaKeyOf(schema)
10006
+ });
10007
+ }
10008
+ return done;
10009
+ }
7986
10010
  removeModule(searchIndex, moduleFilePath);
7987
10011
  var docs = new Set();
7988
- docsByModule.set(moduleFilePath, docs);
10012
+ searchIndex.docsByModule.set(moduleFilePath, docs);
7989
10013
  var path = moduleFilePath + "?p=";
7990
- traverseSchemaSource(source, schema, path, function (_ref) {
7991
- var source = _ref.source,
7992
- schema = _ref.schema,
7993
- path = _ref.path;
7994
- if (source === null) {
7995
- return;
7996
- }
7997
- var searchText = "";
7998
- var label = "";
7999
-
8000
- // Handle primitives
8001
- 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" ||
8002
- // An enum's value is a string the editor chose; it is as searchable as
8003
- // any other string leaf. The old string union was never indexed at all,
8004
- // which is the gap this closes.
8005
- schema.type === "enum" ||
8006
- // A locale tag, for the same reason: it is what the editor picked and
8007
- // what they see in the field, so searching "nb-NO" has to find the
8008
- // Norwegian rows. `traverseSchemaSource` visits these, and visiting a
8009
- // leaf only to index it as the empty string is worse than not visiting
8010
- // it — it looks like content that does not exist.
8011
- schema.type === "locale") {
8012
- var _source$toString, _source$toString2;
8013
- searchText = (_source$toString = source === null || source === void 0 ? void 0 : source.toString()) !== null && _source$toString !== void 0 ? _source$toString : "";
8014
- label = (_source$toString2 = source === null || source === void 0 ? void 0 : source.toString()) !== null && _source$toString2 !== void 0 ? _source$toString2 : "";
8015
- } else if (schema.type === "literal") {
8016
- searchText = schema.value.toString();
8017
- label = schema.value.toString();
8018
- }
8019
- // Handle richtext - flatten to get text content
8020
- else if (schema.type === "richtext") {
8021
- searchText = flattenRichText(source);
8022
- // Use first 50 chars as label
8023
- label = searchText.substring(0, 50) || "richtext";
8024
- }
8025
- // Handle file/image/video
8026
- else if (schema.type === "file" || schema.type === "image" || schema.type === "video") {
8027
- if (source !== null && _typeof(source) === "object" && "path" in source && typeof source.path === "string") {
8028
- // The label is the bare filename - the folder is shown separately in
8029
- // the UI - but both are searchable.
8030
- var _getRefParts = getRefParts(source.path),
8031
- filename = _getRefParts.filename,
8032
- folder = _getRefParts.folder;
8033
- var alt = "alt" in source ? source.alt : "";
8034
- searchText = filename + " " + folder + " " + alt;
8035
- label = filename;
10014
+ traverseSchemaSource(source, schema, path, function (leaf) {
10015
+ var id = indexLeaf(searchIndex, leaf);
10016
+ if (id !== null) docs.add(id);
10017
+ });
10018
+ searchIndex.walkedByModule.set(moduleFilePath, {
10019
+ source: source,
10020
+ schema: schemaKeyOf(schema)
10021
+ });
10022
+ return true;
10023
+ }
10024
+
10025
+ /** How many entries are walked between two looks at the clock. */
10026
+ var ENTRIES_PER_CLOCK_CHECK = 32;
10027
+
10028
+ /**
10029
+ * A module whose root is a record, indexed entry by entry: an entry whose
10030
+ * value is the SAME OBJECT it was indexed from keeps its documents, and only
10031
+ * the others are walked.
10032
+ *
10033
+ * What makes loading a `.jsonValues()` record of 10,000 entries searchable
10034
+ * while it loads. The Studio asks again as each batch lands, and the record's
10035
+ * value is copy-on-write -- an entry nothing touched is the object it was --
10036
+ * so each pass indexes the batch that arrived, not the record. Walked whole,
10037
+ * that was the whole record per batch: quadratic, and a long task each time.
10038
+ *
10039
+ * Identity is only ever a shortcut: a value that is equal but a new object
10040
+ * (a structured clone across a worker, a module read again) is walked again,
10041
+ * never skipped. A different schema starts the module over.
10042
+ */
10043
+ function indexRecordModule(searchIndex, moduleFilePath, source, schema, until) {
10044
+ var index = searchIndex.index,
10045
+ pathToLabel = searchIndex.pathToLabel,
10046
+ docsByModule = searchIndex.docsByModule,
10047
+ entriesByModule = searchIndex.entriesByModule;
10048
+ var indexed = entriesByModule.get(moduleFilePath);
10049
+ var docs = docsByModule.get(moduleFilePath);
10050
+ if (indexed === undefined || docs === undefined || indexed.schema !== schema && schemaKeyOf(indexed.schema) !== schemaKeyOf(schema)) {
10051
+ removeModule(searchIndex, moduleFilePath);
10052
+ indexed = {
10053
+ schema: schema,
10054
+ entries: new Map()
10055
+ };
10056
+ docs = new Set();
10057
+ entriesByModule.set(moduleFilePath, indexed);
10058
+ docsByModule.set(moduleFilePath, docs);
10059
+ }
10060
+ var _indexed = indexed,
10061
+ entries = _indexed.entries;
10062
+ var _iterator = _createForOfIteratorHelper(entries),
10063
+ _step;
10064
+ try {
10065
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
10066
+ var _step$value = _slicedToArray(_step.value, 2),
10067
+ _key = _step$value[0],
10068
+ was = _step$value[1];
10069
+ if (Object.prototype.hasOwnProperty.call(source, _key) && source[_key] === was.source) {
10070
+ continue;
10071
+ }
10072
+ var _iterator2 = _createForOfIteratorHelper(was.docs),
10073
+ _step2;
10074
+ try {
10075
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
10076
+ var _id = _step2.value;
10077
+ index.remove(_id);
10078
+ pathToLabel["delete"](_id);
10079
+ docs["delete"](_id);
10080
+ }
10081
+ } catch (err) {
10082
+ _iterator2.e(err);
10083
+ } finally {
10084
+ _iterator2.f();
8036
10085
  }
10086
+ entries["delete"](_key);
8037
10087
  }
10088
+ } catch (err) {
10089
+ _iterator.e(err);
10090
+ } finally {
10091
+ _iterator.f();
10092
+ }
10093
+ var path = moduleFilePath + "?p=";
10094
+ var walked = 0;
10095
+ var _loop = function _loop() {
10096
+ if (entries.has(key)) return 0; // continue
10097
+ if (until !== undefined && ++walked % ENTRIES_PER_CLOCK_CHECK === 0 && performance.now() > until) {
10098
+ return {
10099
+ v: false
10100
+ };
10101
+ }
10102
+ var value = source[key];
10103
+ var entryDocs = [];
10104
+ traverseSchemaSource(value, schema.item, sourcePathOfChild(path, key), function (leaf) {
10105
+ var id = indexLeaf(searchIndex, leaf);
10106
+ if (id === null) return;
10107
+ entryDocs.push(id);
10108
+ docs.add(id);
10109
+ });
10110
+ entries.set(key, {
10111
+ source: value,
10112
+ docs: entryDocs
10113
+ });
10114
+ },
10115
+ _ret;
10116
+ for (var key in source) {
10117
+ _ret = _loop();
10118
+ if (_ret === 0) continue;
10119
+ if (_ret) return _ret.v;
10120
+ }
10121
+ return true;
10122
+ }
10123
+ function isJsonRecord(source) {
10124
+ return _typeof(source) === "object" && source !== null && !Array.isArray(source);
10125
+ }
10126
+
10127
+ /**
10128
+ * A schema by its content, kept per schema object. Its object is not enough:
10129
+ * the Studio receives a module's schema again (new code, a re-read of the
10130
+ * project) as a new object that says the same, and starting a record of
10131
+ * 10,000 entries over for that was seconds of removing and adding
10132
+ * documents in one go.
10133
+ */
10134
+ var schemaKeys = new WeakMap();
10135
+ function schemaKeyOf(schema) {
10136
+ var key = schemaKeys.get(schema);
10137
+ if (key === undefined) {
10138
+ key = JSON.stringify(schema);
10139
+ schemaKeys.set(schema, key);
10140
+ }
10141
+ return key;
10142
+ }
10143
+
10144
+ /** One leaf as a document, if it has anything to find. Its id, or `null`. */
10145
+ function indexLeaf(searchIndex, _ref) {
10146
+ var source = _ref.source,
10147
+ schema = _ref.schema,
10148
+ path = _ref.path;
10149
+ if (source === null) {
10150
+ return null;
10151
+ }
10152
+ var index = searchIndex.index,
10153
+ pathToLabel = searchIndex.pathToLabel;
10154
+ var searchText = "";
10155
+ var label = "";
8038
10156
 
8039
- // Add to index if we have search text
8040
- if (searchText) {
8041
- var _Internal$splitModule = Internal.splitModuleFilePathAndModulePath(path),
8042
- _Internal$splitModule2 = _slicedToArray(_Internal$splitModule, 2),
8043
- modulePath = _Internal$splitModule2[1];
8044
- var cleanPath = fastRemoveNonWordChars(modulePath) + " ";
8045
- index.add(path, cleanPath + " " + path + " " + searchText);
8046
- pathToLabel.set(path, label);
8047
- docs.add(path);
10157
+ // Handle primitives
10158
+ 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" ||
10159
+ // An enum's value is a string the editor chose; it is as searchable as
10160
+ // any other string leaf. The old string union was never indexed at all,
10161
+ // which is the gap this closes.
10162
+ schema.type === "enum" ||
10163
+ // A locale tag, for the same reason: it is what the editor picked and
10164
+ // what they see in the field, so searching "nb-NO" has to find the
10165
+ // Norwegian rows. `traverseSchemaSource` visits these, and visiting a
10166
+ // leaf only to index it as the empty string is worse than not visiting
10167
+ // it — it looks like content that does not exist.
10168
+ schema.type === "locale") {
10169
+ var _source$toString, _source$toString2;
10170
+ searchText = (_source$toString = source === null || source === void 0 ? void 0 : source.toString()) !== null && _source$toString !== void 0 ? _source$toString : "";
10171
+ label = (_source$toString2 = source === null || source === void 0 ? void 0 : source.toString()) !== null && _source$toString2 !== void 0 ? _source$toString2 : "";
10172
+ } else if (schema.type === "literal") {
10173
+ searchText = schema.value.toString();
10174
+ label = schema.value.toString();
10175
+ }
10176
+ // Handle richtext - flatten to get text content
10177
+ else if (schema.type === "richtext") {
10178
+ searchText = flattenRichText(source);
10179
+ // Use first 50 chars as label
10180
+ label = searchText.substring(0, 50) || "richtext";
10181
+ }
10182
+ // Handle file/image/video
10183
+ else if (schema.type === "file" || schema.type === "image" || schema.type === "video") {
10184
+ if (source !== null && _typeof(source) === "object" && "path" in source && typeof source.path === "string") {
10185
+ // The label is the bare filename - the folder is shown separately in
10186
+ // the UI - but both are searchable.
10187
+ var _getRefParts = getRefParts(source.path),
10188
+ filename = _getRefParts.filename,
10189
+ folder = _getRefParts.folder;
10190
+ var alt = "alt" in source ? source.alt : "";
10191
+ searchText = filename + " " + folder + " " + alt;
10192
+ label = filename;
8048
10193
  }
8049
- });
10194
+ }
10195
+
10196
+ // Add to index if we have search text
10197
+ if (!searchText) {
10198
+ return null;
10199
+ }
10200
+ var _Internal$splitModule = Internal.splitModuleFilePathAndModulePath(path),
10201
+ _Internal$splitModule2 = _slicedToArray(_Internal$splitModule, 2),
10202
+ modulePath = _Internal$splitModule2[1];
10203
+ var cleanPath = fastRemoveNonWordChars(modulePath) + " ";
10204
+ index.add(path, cleanPath + " " + path + " " + searchText);
10205
+ pathToLabel.set(path, label);
10206
+ return path;
8050
10207
  }
8051
10208
 
8052
10209
  /**
@@ -8058,21 +10215,24 @@ function indexModule(searchIndex, moduleFilePath, source, schema) {
8058
10215
  function removeModule(searchIndex, moduleFilePath) {
8059
10216
  var index = searchIndex.index,
8060
10217
  pathToLabel = searchIndex.pathToLabel,
8061
- docsByModule = searchIndex.docsByModule;
10218
+ docsByModule = searchIndex.docsByModule,
10219
+ entriesByModule = searchIndex.entriesByModule;
10220
+ entriesByModule["delete"](moduleFilePath);
10221
+ searchIndex.walkedByModule["delete"](moduleFilePath);
8062
10222
  var existing = docsByModule.get(moduleFilePath);
8063
10223
  if (existing === undefined) return;
8064
- var _iterator = _createForOfIteratorHelper(existing),
8065
- _step;
10224
+ var _iterator3 = _createForOfIteratorHelper(existing),
10225
+ _step3;
8066
10226
  try {
8067
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
8068
- var _id = _step.value;
8069
- index.remove(_id);
8070
- pathToLabel["delete"](_id);
10227
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
10228
+ var _id2 = _step3.value;
10229
+ index.remove(_id2);
10230
+ pathToLabel["delete"](_id2);
8071
10231
  }
8072
10232
  } catch (err) {
8073
- _iterator.e(err);
10233
+ _iterator3.e(err);
8074
10234
  } finally {
8075
- _iterator.f();
10235
+ _iterator3.f();
8076
10236
  }
8077
10237
  docsByModule["delete"](moduleFilePath);
8078
10238
  }
@@ -9547,4 +11707,4 @@ var messageOf = function messageOf(error) {
9547
11707
  return error instanceof Error ? error.message : String(error);
9548
11708
  };
9549
11709
 
9550
- export { AA_LARGE, AA_TEXT, AIToolCallSummaries, AnalyticsBeacon, Api, BRAND_CONTRAST_PAIRS, BinaryFileRef, CodeOptions, ColorOptions, DEFAULT_ERROR_MESSAGE, DateOptions, ENCODE_DEFAULTS, ENCODE_MIME_TYPE_OF, FileOptions, HistoricalCommit, HistoricalModule, HistoricalPatchSet, HistoryError, ImageEncodeOption, ImageOptions, JSONValue, JSON_ENTRIES_BATCH_MAX, LOCAL_FILE_IN_REMOTE_PROJECT_HINT, LOCAL_FILE_IN_REMOTE_PROJECT_WARNING, ModuleComparison, NEVER_ENCODED, ParentRef$1 as ParentRef, Patch, PatchBlock, PatchId, PatchJSON, PatchSets, PlanRefusalError, RAMP_STEPS, RemirrorBr, RemirrorBulletList, RemirrorHeading, RemirrorImage, RemirrorJSON, RemirrorLinkMark, RemirrorListItem, RemirrorOrderedList, RemirrorParagraph, RemirrorText, RemirrorTextMark, RestoreVerdict, RichTextOptions, SCHEMA_SOURCE_FIXES, SELF_CHECK_VERSION, SKETCHPAD_MAX_TEXT_LENGTH, STUDIO_FIXES, SerializedArraySchema, SerializedBooleanSchema, SerializedCodeSchema, SerializedColorSchema, SerializedDateSchema, SerializedDateTimeSchema, SerializedDiscriminatedUnionSchema, SerializedEnumSchema, SerializedFileSchema, SerializedImageSchema, SerializedKeyOfSchema, SerializedLiteralSchema, SerializedLocaleSchema, SerializedNumberSchema, SerializedObjectSchema, SerializedRecordSchema, SerializedRichTextSchema, SerializedRouteSchema, SerializedSchema, SerializedSettingsSchema, SerializedStringSchema, SerializedValViewSchema, SerializedVideoSchema, SharedValConfig, Sketchpad, SketchpadSummary, VAL_ENABLE_COOKIE_NAME, VAL_GREEN_RAMP, VAL_SESSION_COOKIE, VAL_STATE_COOKIE, VAL_STUDIO_HEADER, ValCommit, ValDeployment, VideoOptions, VideoStreamOption, WARNING_FIXES, WASM_ENCODED_MIME_TYPES, accentRamp, buildDuplicatePatch, buildEmptyAtPathPatch, buildFileRefValue, buildImageGalleryPatch, buildRemoveImageGalleryEntryPatch, buildSearchIndex, chainHeadOf, checkCompatibility, checkSketch, childSource, chooseEncoded, concatModulePath, contrastRatio, createRegExpFromPattern, createSearchIndex, createValClient, declaredLocales, describeContainerAtPath, emptyOf, enumLocaleTag, explainIncompatible, fallbackMimeType, filterBlockingValidationErrors, filterRoutesByPatterns, findSimilar, fitWithin, flattenRichText, forEachRichTextImage, formatSketch, formatSketchBox, formatSketchText, getErrorMessageFromUnknownJson, getFilenameFromRef, getInlineImgInfo, getKeyOfRecordAt, getRefParts, getRoutesOf, getRoutesWithModulePaths, getSourceAt, getValDemo, hasLocaleRouteParam, indexModule, isInsidePatchSetPath, isJsonObject, isLocaleSchema, isMimeTypeAccepted, isRemoteSchema, isRepairedOnSave, isSchemaSourceFixError, isSkippedSource, isStudioFix, isValidationWarning, isWarningFix, joinSharedChanges, keyOfRecordPath, levenshtein, localeAt, localeMeantBy, localeOfModuleRoot, localeOfRouteKey, localeParamOfRouteKey, localeScopeAt, memoizePerRequest, newestCommitSha, parseSketch, partitionValidationErrors, performSearch, proposalCustomProperties, relativeLuminance, relocateForLocale, remirrorToRichTextSource, removeModule, resolveEncodeSettings, resolvePairGround, resolveSchemaSourceFixForError, resolveSchemaSourceFixes, resolveSerializedSchemaAtPath, richTextImageSchema, richTextToRemirror, safeParsePatch, schemaErrorsOf, schemaTypesOfPath, selectionClosure, selfCheck, shouldEncodeWithWasm, sideFiles, sideSource, sketchTitle, sourcePathOfChild, themeCustomProperties, toolNames, traverseSchemaSource, urlOf, validateRoutePatterns, withExtension };
11710
+ export { AA_LARGE, AA_TEXT, AIToolCallSummaries, AnalyticsBeacon, Api, BRAND_CONTRAST_PAIRS, BinaryFileRef, ChainFold, CodeOptions, ColorOptions, DEFAULT_ERROR_MESSAGE, DateOptions, EMPTY_CHAIN, ENCODE_DEFAULTS, ENCODE_MIME_TYPE_OF, ENGINE, FileOptions, HistoricalCommit, HistoricalModule, HistoricalPatchSet, HistoryError, ImageEncodeOption, ImageOptions, JSONValue, JSON_ENTRIES_BATCH_MAX, LOCAL_FILE_IN_REMOTE_PROJECT_HINT, LOCAL_FILE_IN_REMOTE_PROJECT_WARNING, ModuleComparison, ModuleVersionSchema, NEVER_ENCODED, ParentRef$1 as ParentRef, Patch, PatchBlock, PatchId, PatchJSON, PatchSets, PlanRefusalError, ProjectVersionSchema, RAMP_STEPS, RemirrorBr, RemirrorBulletList, RemirrorHeading, RemirrorImage, RemirrorJSON, RemirrorLinkMark, RemirrorListItem, RemirrorOrderedList, RemirrorParagraph, RemirrorText, RemirrorTextMark, RestoreVerdict, RichTextOptions, SCHEMA_SOURCE_FIXES, SELF_CHECK_VERSION, SKETCHPAD_MAX_TEXT_LENGTH, STAGED_SCHEMA_PREFIX, STUDIO_FIXES, SerializedArraySchema, SerializedBooleanSchema, SerializedCodeSchema, SerializedColorSchema, SerializedDateSchema, SerializedDateTimeSchema, SerializedDiscriminatedUnionSchema, SerializedEnumSchema, SerializedFileSchema, SerializedImageSchema, SerializedKeyOfSchema, SerializedLiteralSchema, SerializedLocaleSchema, SerializedNumberSchema, SerializedObjectSchema, SerializedRecordSchema, SerializedRichTextSchema, SerializedRouteSchema, SerializedSchema, SerializedSettingsSchema, SerializedStringSchema, SerializedValViewSchema, SerializedVideoSchema, SharedValConfig, Sketchpad, SketchpadSummary, VAL_CLIENT_HEADER, VAL_ENABLE_COOKIE_NAME, VAL_GREEN_RAMP, VAL_SESSION_COOKIE, VAL_STATE_COOKIE, VAL_STUDIO_HEADER, ValCommit, ValDeployment, VideoOptions, VideoStreamOption, WARNING_FIXES, WASM_ENCODED_MIME_TYPES, accentRamp, applyChain, applyPatchRecord, baseModuleContent, baseVersionOf, buildDuplicatePatch, buildEmptyAtPathPatch, buildFileRefValue, buildImageGalleryPatch, buildRemoveImageGalleryEntryPatch, buildSearchIndex, bundleTier, chainHeadOf, chainOf, checkCompatibility, checkSketch, childSource, chooseEncoded, classifyJsonValuesOp, concatModulePath, contrastRatio, createRegExpFromPattern, createSearchIndex, createValClient, declaredLocales, describeContainerAtPath, emptyOf, engineWork, entryAddress, entryState, enumLocaleTag, expandJsonValuesRootOp, expandJsonValuesRootOpForKey, explainIncompatible, extendChain, fallbackMimeType, filterBlockingValidationErrors, filterRoutesByPatterns, findSimilar, fitWithin, flattenRichText, forEachRichTextImage, formatSketch, formatSketchBox, formatSketchText, getErrorMessageFromUnknownJson, getFilenameFromRef, getInlineImgInfo, getKeyOfRecordAt, getRefParts, getRoutesOf, getRoutesWithModulePaths, getSourceAt, getValDemo, hasLocaleRouteParam, indexModule, isInsidePatchSetPath, isJsonObject, isJsonValuesRootOp, isLocaleSchema, isMimeTypeAccepted, isRemoteSchema, isRepairedOnSave, isSchemaSourceFixError, isSkippedSource, isStudioFix, isValidationWarning, isWarningFix, joinSharedChanges, keyOfRecordPath, levenshtein, localeAt, localeMeantBy, localeOfModuleRoot, localeOfRouteKey, localeParamOfRouteKey, localeScopeAt, memoryTier, moduleKey, newestCommitSha, parseModuleKey, parseSketch, partitionValidationErrors, pathOfModuleKey, performSearch, proposalCustomProperties, rebaseContentOp, relativeLuminance, relocateForLocale, remirrorToRichTextSource, removeModule, resolveEncodeSettings, resolveEntry, resolvePairGround, resolveSchemaSourceFixForError, resolveSchemaSourceFixes, resolveSerializedSchemaAtPath, richTextImageSchema, richTextToRemirror, safeParsePatch, sameEntryVersion, sameModuleVersion, schemaErrorsOf, schemaTypesOfPath, schemaVersionOf, selectionClosure, selfCheck, shouldEncodeWithWasm, sideFiles, sideSource, sketchTitle, sourcePathOfChild, storeWork, themeCustomProperties, tiered, toolNames, traverseSchemaSource, urlOf, valClientHeaders, valModulesEntryFile, valModulesEntryPath, validateRoutePatterns, versionHashCount, wholeEntryKey, withExtension };