@valbuild/shared 0.150.20 → 0.152.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 (36) hide show
  1. package/CHANGELOG.md +268 -0
  2. package/client/dist/valbuild-shared-client.cjs.dev.js +1 -1
  3. package/client/dist/valbuild-shared-client.cjs.prod.js +1 -1
  4. package/client/dist/valbuild-shared-client.esm.js +1 -1
  5. package/dist/declarations/src/internal/ApiRoutes.d.ts +270 -2
  6. package/dist/declarations/src/internal/aiTools/toolNames.d.ts +1 -1
  7. package/dist/declarations/src/internal/content/applyChain.d.ts +124 -0
  8. package/dist/declarations/src/internal/content/buildRegistry.d.ts +36 -0
  9. package/dist/declarations/src/internal/content/jsonValuesOps.d.ts +110 -0
  10. package/dist/declarations/src/internal/content/moduleStore.d.ts +119 -0
  11. package/dist/declarations/src/internal/content/source.d.ts +71 -0
  12. package/dist/declarations/src/internal/content/version.d.ts +151 -0
  13. package/dist/declarations/src/internal/index.d.ts +8 -1
  14. package/dist/declarations/src/internal/publish/projectSource.d.ts +28 -0
  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/sketchpad/sketchFormat.d.ts +41 -6
  18. package/dist/declarations/src/internal/valCanvasProtocol.d.ts +46 -0
  19. package/dist/declarations/src/internal/valClientHeader.d.ts +36 -0
  20. package/dist/declarations/src/publish/callJson.d.ts +15 -0
  21. package/dist/declarations/src/publish/jobClient.d.ts +7 -1
  22. package/dist/declarations/src/publish/publishClient.d.ts +2 -14
  23. package/dist/{getSitemapTree-fef55872.esm.js → getSitemapTree-3c1b9e57.esm.js} +18 -2
  24. package/dist/{getSitemapTree-6dfa2f4e.cjs.dev.js → getSitemapTree-59c5ed5d.cjs.dev.js} +18 -2
  25. package/dist/{getSitemapTree-8a04f3cf.cjs.prod.js → getSitemapTree-6b9024d1.cjs.prod.js} +18 -2
  26. package/dist/{protocol-d0efb8cd.cjs.dev.js → projectSource-2a58ef15.cjs.dev.js} +221 -0
  27. package/dist/{protocol-55cfa3cf.esm.js → projectSource-7f0ee83c.esm.js} +216 -1
  28. package/dist/{protocol-b1055ef0.cjs.prod.js → projectSource-ed6aec39.cjs.prod.js} +221 -0
  29. package/internal/dist/valbuild-shared-internal.cjs.dev.js +2521 -192
  30. package/internal/dist/valbuild-shared-internal.cjs.prod.js +2521 -192
  31. package/internal/dist/valbuild-shared-internal.esm.js +2463 -175
  32. package/package.json +3 -3
  33. package/publish/dist/valbuild-shared-publish.cjs.dev.js +48 -155
  34. package/publish/dist/valbuild-shared-publish.cjs.prod.js +48 -155
  35. package/publish/dist/valbuild-shared-publish.esm.js +17 -123
  36. 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, 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-fef55872.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: {
@@ -2745,6 +3079,26 @@ var Api = {
2745
3079
  parentRef: ParentRef,
2746
3080
  headVersion: z.number().optional()
2747
3081
  })
3082
+ }),
3083
+ /*
3084
+ * The content service failed to take a patch: it answered with a
3085
+ * server error, or could not be reached. Not the 400 above either,
3086
+ * because the patches are fine -- the Studio keeps them and offers
3087
+ * to send them again. `message` is for the editor and `details` (the
3088
+ * status line and the service's own words) for whoever they ask.
3089
+ *
3090
+ * As with the 403, the patches BEFORE the failed one were saved.
3091
+ */
3092
+ z.object({
3093
+ status: z.literal(502),
3094
+ json: z.object({
3095
+ type: z.literal("service-error"),
3096
+ message: z.string(),
3097
+ details: z.string().optional(),
3098
+ newPatchIds: z.array(PatchId),
3099
+ parentRef: ParentRef,
3100
+ headVersion: z.number().optional()
3101
+ })
2748
3102
  }), z.object({
2749
3103
  status: z.literal(200),
2750
3104
  json: z.object({
@@ -2780,7 +3134,9 @@ var Api = {
2780
3134
  status: z.literal(500),
2781
3135
  json: z.object({
2782
3136
  message: z.string(),
2783
- error: GenericError
3137
+ error: GenericError,
3138
+ /** What the content service said: its status line and words. */
3139
+ details: z.string().optional()
2784
3140
  })
2785
3141
  }), z.object({
2786
3142
  status: z.literal(200),
@@ -2797,7 +3153,9 @@ var Api = {
2797
3153
  })),
2798
3154
  baseSha: z.string(),
2799
3155
  error: GenericError.optional(),
2800
- errors: z.record(PatchId, GenericError).optional()
3156
+ errors: z.record(PatchId, GenericError).optional(),
3157
+ /** The project version these patches were read at. See `/stat`. */
3158
+ version: ProjectVersionSchema.optional()
2801
3159
  })
2802
3160
  })])
2803
3161
  }
@@ -2830,6 +3188,15 @@ var Api = {
2830
3188
  })])
2831
3189
  }
2832
3190
  },
3191
+ /**
3192
+ * @deprecated The whole tree. Nothing of this release asks it: the Studio
3193
+ * reads modules by version (`GET /manifest`, `GET /modules/~`), the draft
3194
+ * readers and MCP read the head in-process. Kept, with `exclude_patches`
3195
+ * (`BaseAlignment`'s fetch) and the ignored `validate_sources` and
3196
+ * `validate_binary_files`, for a Studio of the release before; goes in the
3197
+ * release after the one that ships this (see "Release-delayed deletions"
3198
+ * in `docs/plans/one-content-model.md`).
3199
+ */
2833
3200
  "/sources/~": {
2834
3201
  PUT: {
2835
3202
  req: {
@@ -2881,9 +3248,97 @@ var Api = {
2881
3248
  skipped: z.array(PatchId).optional(),
2882
3249
  errors: z.record(PatchId, GenericError).optional()
2883
3250
  }).optional(),
2884
- validationErrors: z.record(z.string(), z.array(ValidationError)).optional()
2885
- }))
3251
+ validationErrors: z.record(z.string(), z.array(ValidationError)).optional(),
3252
+ /**
3253
+ * The version of `source` as answered: with the chain the
3254
+ * server applied, or the empty chain when it applied none
3255
+ * (`apply_patches=false`). Optional: an older server sends
3256
+ * none.
3257
+ */
3258
+ version: ModuleVersionSchema.optional()
3259
+ })),
3260
+ /** The project version this tree was read at. See `/stat`. */
3261
+ project: ProjectVersionSchema.optional()
3262
+ })
3263
+ })])
3264
+ }
3265
+ },
3266
+ /**
3267
+ * One module by version: phase 6 of `docs/plans/one-content-model.md`
3268
+ * ("The Studio loads everything from one place"). `GET /modules/~<path>`,
3269
+ * with `v` the version wanted as its `moduleKey` (the newest when absent;
3270
+ * a base is a version with the empty chain) and `have` the version the
3271
+ * caller holds: when the caller's chain is a start of the answer's under
3272
+ * the same schema and base, the answer is the patches from it (`delta`),
3273
+ * otherwise the whole module. An answer for an asked `v` is immutable.
3274
+ */
3275
+ "/modules/~": {
3276
+ GET: {
3277
+ req: {
3278
+ path: z.string(),
3279
+ query: {
3280
+ v: onlyOneStringQueryParam.optional(),
3281
+ have: onlyOneStringQueryParam.optional()
3282
+ },
3283
+ cookies: {
3284
+ val_session: z.string().optional()
3285
+ }
3286
+ },
3287
+ res: z.union([unauthorizedResponse, z.object({
3288
+ status: z.literal(404),
3289
+ json: z.object({
3290
+ message: z.string(),
3291
+ /** The version held now, when the one asked for is not. */
3292
+ current: ModuleVersionSchema.optional()
2886
3293
  })
3294
+ }), z.object({
3295
+ status: z.literal(500),
3296
+ json: GenericError
3297
+ }), z.object({
3298
+ status: z.literal(200),
3299
+ headers: z.object({
3300
+ "Cache-Control": z.string()
3301
+ }).optional(),
3302
+ json: z.union([z.object({
3303
+ kind: z.literal("module"),
3304
+ version: ModuleVersionSchema,
3305
+ source: z.any(),
3306
+ /**
3307
+ * The `.jsonValues()` entries the module's chain touched, read,
3308
+ * each with its own version. Every other entry is the base's,
3309
+ * behind its marker in `source`.
3310
+ */
3311
+ entries: z.record(z.string(), z.union([z.object({
3312
+ content: z.any(),
3313
+ version: EntryVersionSchema
3314
+ }), z.object({
3315
+ deleted: z.literal(true),
3316
+ version: EntryVersionSchema
3317
+ }), z.object({
3318
+ error: z.string(),
3319
+ /** The patch that made it unreadable, when known. */
3320
+ patchId: PatchId.optional(),
3321
+ version: EntryVersionSchema
3322
+ })])),
3323
+ /**
3324
+ * The patch ids its chain is made of, in chain order: what a
3325
+ * client holding this knows the server already has. Optional:
3326
+ * a server of the release before sends none.
3327
+ */
3328
+ patches: z.array(PatchId).optional(),
3329
+ failed: z.object({
3330
+ patchId: PatchId,
3331
+ message: z.string()
3332
+ }).optional()
3333
+ }), z.object({
3334
+ kind: z.literal("delta"),
3335
+ from: ModuleVersionSchema,
3336
+ to: ModuleVersionSchema,
3337
+ patches: z.array(z.object({
3338
+ patchId: PatchId,
3339
+ patch: Patch
3340
+ }))
3341
+ })])
2887
3342
  })])
2888
3343
  }
2889
3344
  },
@@ -3607,7 +4062,14 @@ var Api = {
3607
4062
  path: ModuleFilePath,
3608
4063
  key: z.string(),
3609
4064
  // The entry's JSON content (or null if the entry has no value).
3610
- content: z.any()
4065
+ content: z.any(),
4066
+ /**
4067
+ * The version of the record module the entry was read from, with
4068
+ * the chain that was applied (the empty chain without
4069
+ * `apply_patches`). An entry's own version comes with entries
4070
+ * stored as modules of their own.
4071
+ */
4072
+ version: ModuleVersionSchema.optional()
3611
4073
  })
3612
4074
  }),
3613
4075
  // Shapes (2) and (3): `keys` or `offset`+`limit`.
@@ -3630,7 +4092,9 @@ var Api = {
3630
4092
  // All-mode only: the resolved window and the record's total key count.
3631
4093
  offset: z.number().optional(),
3632
4094
  limit: z.number().optional(),
3633
- total: z.number().optional()
4095
+ total: z.number().optional(),
4096
+ /** As for a single `key`. */
4097
+ version: ModuleVersionSchema.optional()
3634
4098
  })
3635
4099
  }), z.object({
3636
4100
  status: z.literal(400),
@@ -4093,8 +4557,8 @@ var createValClient = function createValClient(host, config, options) {
4093
4557
  body: anyReq.body !== undefined ? JSON.stringify(anyReq.body) : undefined
4094
4558
  }).then(/*#__PURE__*/function () {
4095
4559
  var _ref2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(res) {
4096
- var _apiEndpoint$res;
4097
- var contentTypeHeaders, json, valClientResult, message, responseResult;
4560
+ var _apiEndpoint$res2;
4561
+ var contentTypeHeaders, _apiEndpoint$res, notModified, json, valClientResult, message, responseResult;
4098
4562
  return _regenerator().w(function (_context) {
4099
4563
  while (1) switch (_context.n) {
4100
4564
  case 0:
@@ -4112,10 +4576,38 @@ var createValClient = function createValClient(host, config, options) {
4112
4576
  }
4113
4577
  });
4114
4578
  case 1:
4115
- if (contentTypeHeaders !== null && contentTypeHeaders !== void 0 && contentTypeHeaders.includes("application/json")) {
4579
+ if (!(res.status === 304)) {
4580
+ _context.n = 3;
4581
+ break;
4582
+ }
4583
+ notModified = {
4584
+ status: 304,
4585
+ json: null
4586
+ };
4587
+ if (!(((_apiEndpoint$res = apiEndpoint.res) === null || _apiEndpoint$res === void 0 ? void 0 : _apiEndpoint$res.safeParse(notModified).success) === false)) {
4116
4588
  _context.n = 2;
4117
4589
  break;
4118
4590
  }
4591
+ return _context.a(2, {
4592
+ status: null,
4593
+ json: {
4594
+ type: "client_side_validation_error",
4595
+ message: "Invalid response (304 from a route that does not answer one). This could be a result of mismatched Val versions.",
4596
+ details: {
4597
+ validationError: "Unexpected 304",
4598
+ data: {
4599
+ status: res.status
4600
+ }
4601
+ }
4602
+ }
4603
+ });
4604
+ case 2:
4605
+ return _context.a(2, notModified);
4606
+ case 3:
4607
+ if (contentTypeHeaders !== null && contentTypeHeaders !== void 0 && contentTypeHeaders.includes("application/json")) {
4608
+ _context.n = 4;
4609
+ break;
4610
+ }
4119
4611
  return _context.a(2, {
4120
4612
  status: null,
4121
4613
  json: {
@@ -4130,17 +4622,17 @@ var createValClient = function createValClient(host, config, options) {
4130
4622
  }
4131
4623
  }
4132
4624
  });
4133
- case 2:
4134
- _context.n = 3;
4625
+ case 4:
4626
+ _context.n = 5;
4135
4627
  return res.json();
4136
- case 3:
4628
+ case 5:
4137
4629
  json = _context.v;
4138
4630
  valClientResult = {
4139
4631
  status: res.status,
4140
4632
  json: json
4141
4633
  };
4142
4634
  if (!(res.status === 500)) {
4143
- _context.n = 4;
4635
+ _context.n = 6;
4144
4636
  break;
4145
4637
  }
4146
4638
  Internal.log.error("Server responded with an error", json);
@@ -4152,10 +4644,10 @@ var createValClient = function createValClient(host, config, options) {
4152
4644
  message: message
4153
4645
  }
4154
4646
  });
4155
- case 4:
4156
- responseResult = (_apiEndpoint$res = apiEndpoint.res) === null || _apiEndpoint$res === void 0 ? void 0 : _apiEndpoint$res.safeParse(valClientResult);
4647
+ case 6:
4648
+ responseResult = (_apiEndpoint$res2 = apiEndpoint.res) === null || _apiEndpoint$res2 === void 0 ? void 0 : _apiEndpoint$res2.safeParse(valClientResult);
4157
4649
  if (!(responseResult && !responseResult.success)) {
4158
- _context.n = 5;
4650
+ _context.n = 7;
4159
4651
  break;
4160
4652
  }
4161
4653
  return _context.a(2, {
@@ -4169,7 +4661,7 @@ var createValClient = function createValClient(host, config, options) {
4169
4661
  }
4170
4662
  }
4171
4663
  });
4172
- case 5:
4664
+ case 7:
4173
4665
  return _context.a(2, {
4174
4666
  status: res.status,
4175
4667
  json: json
@@ -4313,6 +4805,44 @@ var urlOf = function urlOf() {
4313
4805
  return "".concat(route);
4314
4806
  };
4315
4807
 
4808
+ /**
4809
+ * Which Val package, at which version, sent a request to Val Build.
4810
+ *
4811
+ * Sent on every request a Val package makes to Val's own services from a
4812
+ * server or a CLI -- the content host, `/api/login`, the analytics ingest --
4813
+ * so Val Build can tell which versions are still out there and which old
4814
+ * endpoints they still call. Without it an endpoint can never be retired with
4815
+ * any confidence: a request from a site nobody has upgraded in a year looks
4816
+ * exactly like one from yesterday's release.
4817
+ *
4818
+ * The value is a product token, `@valbuild/server@0.152.0`: the package whose
4819
+ * code built the request, because that is what decides which endpoints are
4820
+ * called. Not the framework binding or the app: those do not choose URLs.
4821
+ *
4822
+ * NOT sent from the browser. A custom header on a cross-origin request makes
4823
+ * the browser send a CORS preflight, and a host that does not list the header
4824
+ * in `Access-Control-Allow-Headers` refuses the request outright -- so adding
4825
+ * it there is a change to every such host first.
4826
+ *
4827
+ * It identifies the code, not the caller: anyone can send any value, so it is
4828
+ * for counting, never for deciding what a request may do.
4829
+ */
4830
+ var VAL_CLIENT_HEADER = "x-val-client";
4831
+
4832
+ /** Packages that send {@link VAL_CLIENT_HEADER}. */
4833
+
4834
+ /**
4835
+ * `{ "x-val-client": "@valbuild/server@0.152.0" }`, to spread into a
4836
+ * request's headers.
4837
+ *
4838
+ * A version that could not be read is sent as `unknown` rather than leaving
4839
+ * the header out: "a server too broken to know its version" is still a thing
4840
+ * worth counting, and it is not the same as "a version older than the header".
4841
+ */
4842
+ function valClientHeaders(pkg, version) {
4843
+ return _defineProperty({}, VAL_CLIENT_HEADER, "".concat(pkg, "@").concat(version || "unknown"));
4844
+ }
4845
+
4316
4846
  /**
4317
4847
  * Can this value, taken from one schema, be written into another?
4318
4848
  *
@@ -4746,10 +5276,1504 @@ function chainHeadOf(reportedHead, listed) {
4746
5276
  return (_listed = listed[listed.length - 1]) !== null && _listed !== void 0 ? _listed : null;
4747
5277
  }
4748
5278
 
4749
- var SharedValConfig = z.object({
4750
- contentHostUrl: z.string().optional(),
4751
- appHostUrl: z.string().optional(),
4752
- project: z.string().optional(),
5279
+ /**
5280
+ * Where a build's `val.modules` registry can be imported from: phase 9 of
5281
+ * `docs/plans/one-content-model.md`, "Reloading the schema while the Studio
5282
+ * runs".
5283
+ *
5284
+ * A build made by `@valbuild/tanstack-build` (every build the platform
5285
+ * serves) emits its `val.modules` as an ES module of its own beside its
5286
+ * client chunks, named after the build: `/_app/val-modules-<build>.js`,
5287
+ * whose default export is the registry. The server running that build knows
5288
+ * its name (`VAL_BUILD`, Val's `publishBuild`) and names the entry on
5289
+ * `GET /manifest` (`registry`), so a Studio opened on an older build imports
5290
+ * the new one's code and reloads it in place (`HostStore.reloadCode`)
5291
+ * instead of being told to reload the page.
5292
+ *
5293
+ * The convention is the builder's and this file's together: the builder
5294
+ * writes the file (`valModulesEntryFile` in `@valbuild/tanstack-build`, which
5295
+ * does not depend on this package), and each side pins the same literal,
5296
+ * `val-modules-<build>.js` (`buildRegistry.test.ts` here, `valModules.test.ts`
5297
+ * there).
5298
+ */
5299
+
5300
+ /** The file a build's registry is emitted as, among its client chunks. */
5301
+ function valModulesEntryFile(build) {
5302
+ return "val-modules-".concat(build, ".js");
5303
+ }
5304
+
5305
+ /** Where a build served by the platform serves its registry. */
5306
+ function valModulesEntryPath(build) {
5307
+ return "/_app/".concat(valModulesEntryFile(build));
5308
+ }
5309
+
5310
+ /**
5311
+ * What `GET /manifest` says about the code of the build that answered:
5312
+ * where its registry is, and which Val it runs. A Studio running another
5313
+ * Val cannot take the build's code in (its own stores would run against a
5314
+ * new core), so it reloads itself instead (`other-val-version`).
5315
+ */
5316
+
5317
+ /*
5318
+ * How a patch op relates to a `.jsonValues()` record: which ops reach into an
5319
+ * entry, what a write of the whole record says about each entry, and how an
5320
+ * op inside an entry is rebased onto the entry's own file.
5321
+ *
5322
+ * Here, in shared, because the engine (`applyChain.ts`) is built on them and
5323
+ * runs wherever content is patched; the commit flow (`ValOps.prepare`), the
5324
+ * CLI's fixes and the language server use the same functions, through
5325
+ * `@valbuild/server`, which re-exports them. One rule, one implementation:
5326
+ * a draft that shows one thing while publishing writes another is the failure
5327
+ * nobody sees until it is published.
5328
+ */
5329
+
5330
+ /**
5331
+ * Classification of a single patch op against a module's serialized schema,
5332
+ * used by the commit flow to route ops for `.jsonValues()` records:
5333
+ *
5334
+ * - `normal`: the op does not descend into a `.jsonValues()` entry; apply it to
5335
+ * the `.val.ts` as usual.
5336
+ * - `entry`: the op targets a `.jsonValues()` entry. `recordPath` is the path to
5337
+ * the record within the module source (empty for a root record/router),
5338
+ * `entryKey` is the entry key, and `subPath` is the remaining path inside the
5339
+ * entry (empty when the op targets the entry value itself, e.g. add/remove of
5340
+ * the whole entry).
5341
+ */
5342
+
5343
+ /**
5344
+ * Walks the serialized schema following the op path. When a `.jsonValues()`
5345
+ * record is encountered, the next path segment is the entry key and everything
5346
+ * after it lives inside the entry's `*.val.json` (so it does not touch the
5347
+ * `.val.ts`). Returns `{ kind: "normal" }` when the op never enters a
5348
+ * `.jsonValues()` record.
5349
+ */
5350
+ function classifyJsonValuesOp(schema, opPath) {
5351
+ var current = schema;
5352
+ var recordPath = [];
5353
+ for (var i = 0; i < opPath.length; i++) {
5354
+ if (!current) {
5355
+ return {
5356
+ kind: "normal"
5357
+ };
5358
+ }
5359
+ if (current.type === "record" && current.jsonValues) {
5360
+ return {
5361
+ kind: "entry",
5362
+ recordPath: recordPath.slice(),
5363
+ entryKey: opPath[i],
5364
+ subPath: opPath.slice(i + 1)
5365
+ };
5366
+ }
5367
+ var seg = opPath[i];
5368
+ current = descend(current, seg);
5369
+ recordPath.push(seg);
5370
+ }
5371
+ return {
5372
+ kind: "normal"
5373
+ };
5374
+ }
5375
+ function descend(schema, key) {
5376
+ switch (schema.type) {
5377
+ case "object":
5378
+ return schema.items[key];
5379
+ case "record":
5380
+ return schema.item;
5381
+ case "array":
5382
+ return schema.item;
5383
+ default:
5384
+ // Unions / primitives / leaf schemas: we cannot (or need not) descend
5385
+ // further to find a jsonValues record. Anything below is a normal
5386
+ // `.val.ts` edit.
5387
+ return undefined;
5388
+ }
5389
+ }
5390
+
5391
+ /**
5392
+ * Is this op a write of the WHOLE record at the root of a `.jsonValues()`
5393
+ * module?
5394
+ *
5395
+ * The one op {@link classifyJsonValuesOp} cannot classify: it finds the entry
5396
+ * key by walking the op path, and the root path has no segments to walk, so a
5397
+ * root write reads as `normal` and gets applied to the `.val.ts` - writing
5398
+ * markers over the `c.json(() => import(...))` calls that make the entries load
5399
+ * at all. {@link expandJsonValuesRootOp} turns it into ops that DO name a key,
5400
+ * which everything downstream already handles.
5401
+ *
5402
+ * `add` counts as well as `replace`: at the root both mean "the document is now
5403
+ * this" (see `JSONOps`), so both have to be expanded or the unexpanded one is
5404
+ * the same bug again.
5405
+ *
5406
+ * The value has to BE a record, and that is part of the question rather than a
5407
+ * check inside the expansion: a `.jsonValues()` record can be nullable, and
5408
+ * `null` at the root means the module no longer has a record at all - there are
5409
+ * no entries to write, and it is an ordinary `.val.ts` write, exactly as it was
5410
+ * before any of this existed. Same for any other non-record value: not a
5411
+ * whole-record write, so not this conversion's to route.
5412
+ */
5413
+ function isJsonValuesRootOp(schema, op) {
5414
+ return (op.op === "replace" || op.op === "add") && op.path.length === 0 && isJsonValuesRecord(op.value) && schema.type === "record" && schema.jsonValues === true;
5415
+ }
5416
+ function isJsonValuesRecord(value) {
5417
+ return _typeof(value) === "object" && value !== null && !Array.isArray(value);
5418
+ }
5419
+
5420
+ /**
5421
+ * One entry as it stands right now, for {@link expandJsonValuesRootOp} to
5422
+ * expand against.
5423
+ *
5424
+ * The key set decides add-vs-replace-vs-remove. The content, where the caller
5425
+ * has it, decides whether an op is emitted at all: an entry whose content is
5426
+ * already what the root write says is left alone rather than rewritten, so
5427
+ * putting a module back does not touch every `*.val.json` it did not change.
5428
+ * `undefined` means "this entry exists, content unknown" - which is what the
5429
+ * Studio's draft source has, because the source holds markers - and yields a
5430
+ * `replace`, the safe answer.
5431
+ */
5432
+
5433
+ /**
5434
+ * Fans a whole-record write at a `.jsonValues()` module's root out into ops
5435
+ * that each name an entry key.
5436
+ *
5437
+ * This is THE conversion, and it has exactly one implementation on purpose: the
5438
+ * commit flow (`ValOps.prepare`) and the engine that builds draft content
5439
+ * (`applyPatchRecord`) both expand through here, so a draft
5440
+ * cannot show something other than what publishing writes. A second
5441
+ * implementation of the same rule would differ silently, which is the whole
5442
+ * failure mode this exists to prevent.
5443
+ *
5444
+ * The value must be the entries' CONTENT. A module's Source is markers, not
5445
+ * content, so a Source handed over here is refused rather than written: that is
5446
+ * the shape of the original bug (a revert replaying the archived Source), and
5447
+ * it is not recoverable afterwards - the markers replace the entries' content
5448
+ * on disk.
5449
+ */
5450
+ function expandJsonValuesRootOp(op, currentEntries) {
5451
+ var valueRes = jsonValuesRootValue(op);
5452
+ if (result.isErr(valueRes)) {
5453
+ return valueRes;
5454
+ }
5455
+ var value = valueRes.value;
5456
+ var expanded = [];
5457
+ for (var _i = 0, _Object$keys = Object.keys(value); _i < _Object$keys.length; _i++) {
5458
+ var entryKey = _Object$keys[_i];
5459
+ var write = writeForEntry(value, entryKey, currentEntries);
5460
+ if (write !== null) {
5461
+ expanded.push(write);
5462
+ }
5463
+ }
5464
+ var _iterator = _createForOfIteratorHelper(currentEntries.keys()),
5465
+ _step;
5466
+ try {
5467
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
5468
+ var _entryKey = _step.value;
5469
+ if (!Object.prototype.hasOwnProperty.call(value, _entryKey)) {
5470
+ expanded.push({
5471
+ op: "remove",
5472
+ path: [_entryKey]
5473
+ });
5474
+ }
5475
+ }
5476
+ } catch (err) {
5477
+ _iterator.e(err);
5478
+ } finally {
5479
+ _iterator.f();
5480
+ }
5481
+ return result.ok(expanded);
5482
+ }
5483
+
5484
+ /**
5485
+ * What a whole-record write says about ONE entry: the same rule
5486
+ * {@link expandJsonValuesRootOp} applies to all of them.
5487
+ *
5488
+ * For a reader that is about one entry. Expanding the whole record to find the
5489
+ * one op that names its key costs an op per entry per entry read - the record
5490
+ * squared, on exactly the large records this feature is for. The rule itself is
5491
+ * `writeForEntry`, shared with the expansion above, so the two cannot come to
5492
+ * disagree; only the loop around it differs.
5493
+ *
5494
+ * `null` means the write says nothing about this entry: it holds that content
5495
+ * already, or the record does not name it and it does not exist.
5496
+ */
5497
+ function expandJsonValuesRootOpForKey(op, entryKey, currentEntries) {
5498
+ var valueRes = jsonValuesRootValue(op);
5499
+ if (result.isErr(valueRes)) {
5500
+ return valueRes;
5501
+ }
5502
+ var value = valueRes.value;
5503
+ if (Object.prototype.hasOwnProperty.call(value, entryKey)) {
5504
+ return result.ok(writeForEntry(value, entryKey, currentEntries));
5505
+ }
5506
+ if (currentEntries.has(entryKey)) {
5507
+ return result.ok({
5508
+ op: "remove",
5509
+ path: [entryKey]
5510
+ });
5511
+ }
5512
+ return result.ok(null);
5513
+ }
5514
+
5515
+ /**
5516
+ * The record a whole-record write carries, once it is known to be one.
5517
+ *
5518
+ * Every entry's value is checked here rather than where each one is written:
5519
+ * a marker anywhere in the record means the writer handed over a module's
5520
+ * Source, and the answer to that is to refuse the write, not to write the rest
5521
+ * of it. Both entry points ask, so an entry read reports it too rather than
5522
+ * showing a draft of a patch that publishing will refuse.
5523
+ */
5524
+ function jsonValuesRootValue(op) {
5525
+ if (op.op !== "replace" && op.op !== "add") {
5526
+ return result.err(new PatchError("Cannot '".concat(op.op, "' the root of a .jsonValues() record: only add and replace write the whole record")));
5527
+ }
5528
+ var value = op.value;
5529
+ if (!isJsonValuesRecord(value)) {
5530
+ 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"));
5531
+ }
5532
+ for (var _i2 = 0, _Object$keys2 = Object.keys(value); _i2 < _Object$keys2.length; _i2++) {
5533
+ var entryKey = _Object$keys2[_i2];
5534
+ /*
5535
+ * A marker is what a module's Source holds WHERE THE CONTENT IS NOT: it
5536
+ * says the content is in the entry's own file. Handed back as an entry's
5537
+ * value - which is what replaying an archived Source does - it would
5538
+ * replace that content with the note saying where it used to be, and
5539
+ * nothing downstream could tell the difference.
5540
+ */
5541
+ if (Internal.isJson(value[entryKey])) {
5542
+ 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")));
5543
+ }
5544
+ }
5545
+ return result.ok(value);
5546
+ }
5547
+
5548
+ /** THE rule, for one entry the record names. `null` = nothing to write. */
5549
+ function writeForEntry(value, entryKey, currentEntries) {
5550
+ var content = value[entryKey];
5551
+ if (!currentEntries.has(entryKey)) {
5552
+ return {
5553
+ op: "add",
5554
+ path: [entryKey],
5555
+ value: content
5556
+ };
5557
+ }
5558
+ var current = currentEntries.get(entryKey);
5559
+ if (current !== undefined && deepEqual(current, content)) {
5560
+ // Unchanged: emitting a replace here would rewrite the entry's file with
5561
+ // the bytes it already holds, so putting a module back would show up as a
5562
+ // change to every entry in it.
5563
+ return null;
5564
+ }
5565
+ return {
5566
+ op: "replace",
5567
+ path: [entryKey],
5568
+ value: content
5569
+ };
5570
+ }
5571
+
5572
+ /**
5573
+ * Rebases a patch op that targets a `.jsonValues()` entry's content so its paths
5574
+ * are relative to the entry's `*.val.json` root (drops the record + entry-key
5575
+ * prefix). Used to replay the op against the backing JSON file.
5576
+ */
5577
+ function rebaseContentOp(op, prefixLen) {
5578
+ var path = op.path.slice(prefixLen);
5579
+ switch (op.op) {
5580
+ case "add":
5581
+ case "replace":
5582
+ case "test":
5583
+ return result.ok(_objectSpread2(_objectSpread2({}, op), {}, {
5584
+ path: path
5585
+ }));
5586
+ case "remove":
5587
+ {
5588
+ if (!array.isNonEmpty(path)) {
5589
+ return result.err(new PatchError("Cannot remove the root of a jsonValues entry"));
5590
+ }
5591
+ return result.ok(_objectSpread2(_objectSpread2({}, op), {}, {
5592
+ path: path
5593
+ }));
5594
+ }
5595
+ case "move":
5596
+ {
5597
+ var from = op.from.slice(prefixLen);
5598
+ if (!array.isNonEmpty(from)) {
5599
+ return result.err(new PatchError("Cannot move from the root of a jsonValues entry"));
5600
+ }
5601
+ return result.ok(_objectSpread2(_objectSpread2({}, op), {}, {
5602
+ path: path,
5603
+ from: from
5604
+ }));
5605
+ }
5606
+ case "copy":
5607
+ return result.ok(_objectSpread2(_objectSpread2({}, op), {}, {
5608
+ path: path,
5609
+ from: op.from.slice(prefixLen)
5610
+ }));
5611
+ case "file":
5612
+ // A file op carries bytes, not content. The binary goes through the
5613
+ // normal upload pipeline; what lands in the entry is the `patch_id` that
5614
+ // marks the bytes as not yet committed, which the caller writes.
5615
+ return result.err(new PatchError("A file op is not a content op: rebase it via its path"));
5616
+ }
5617
+ }
5618
+
5619
+ /**
5620
+ * The entry a path names when it names a WHOLE entry of the root
5621
+ * `.jsonValues()` record, and `undefined` for anything else.
5622
+ */
5623
+ function wholeEntryKey(serializedSchema, path) {
5624
+ if (!serializedSchema) {
5625
+ return undefined;
5626
+ }
5627
+ var cls = classifyJsonValuesOp(serializedSchema, path);
5628
+ return cls.kind === "entry" && cls.recordPath.length === 0 && cls.subPath.length === 0 ? cls.entryKey : undefined;
5629
+ }
5630
+
5631
+ /*
5632
+ * THE engine: one function that turns a module's content and a patch into
5633
+ * the module's next content. See `docs/plans/one-content-model.md`, "One
5634
+ * engine".
5635
+ *
5636
+ * It owns the rules the server's two readers used to follow each in their own
5637
+ * way (`ValOps.getSources` for the module, and a replay of its own for a
5638
+ * `.jsonValues()` entry, kept test-only in `legacyEngine.ts` in
5639
+ * `@valbuild/server` for the conformance cases):
5640
+ *
5641
+ * - where a file's `patch_id` lands: the field's path plus `nestedFilePath`,
5642
+ * after every other op of the patch;
5643
+ * - what an edit inside an entry does: applied to the entry's content when it
5644
+ * is loaded, held (`waiting`) until it is when it is not;
5645
+ * - what a whole entry added does: a marker in the module, content in the
5646
+ * entry;
5647
+ * - that a module's chain stops at its first failing patch, and that the
5648
+ * entries stop with it.
5649
+ *
5650
+ * Copy-on-write along each op's path: nothing is mutated, so a subtree no op
5651
+ * touched keeps its identity (which makes "did this path change?" a `===`),
5652
+ * and a patch costs the depth of its path, not the module's size. Every
5653
+ * container it copies is counted ({@link engineWork}).
5654
+ */
5655
+
5656
+ /** What one `.jsonValues()` entry is at a point in a module's chain. */
5657
+
5658
+ /**
5659
+ * The ops held for an entry, newest first: a list that is extended without
5660
+ * copying, so a chain of N edits to an entry nobody loaded costs N, not N².
5661
+ */
5662
+
5663
+ /**
5664
+ * A module's content: its source, and what its `.jsonValues()` entries are.
5665
+ *
5666
+ * `entries` holds only the entries the chain has touched. An entry it has
5667
+ * not is the base's: `waiting` on its own key with nothing held, as long as
5668
+ * the source has its marker ({@link entryState}). So a module with no
5669
+ * entries, or a chain that never touches one, costs nothing here.
5670
+ */
5671
+
5672
+ /** One patch of a module's chain. */
5673
+
5674
+ var NO_ENTRIES = new Map();
5675
+
5676
+ /** A module's content with nothing applied yet: its base. */
5677
+ function baseModuleContent(source) {
5678
+ return {
5679
+ source: source,
5680
+ entries: NO_ENTRIES
5681
+ };
5682
+ }
5683
+
5684
+ // #region counted work
5685
+
5686
+ /**
5687
+ * What a copied container costs, per slot copied (an object's entry or an
5688
+ * array's element): one reference. Strings and numbers are shared, never
5689
+ * copied, so this is what a copy really moves -- and what the whole module
5690
+ * cost per patch before copy-on-write (`applyPatch(deepClone(module))`).
5691
+ */
5692
+ var SLOT_BYTES = 8;
5693
+ var applies = 0;
5694
+ var clonedBytes = 0;
5695
+
5696
+ /**
5697
+ * The work the engine has done in this process: patches applied
5698
+ * (`engine:apply-patch`) and bytes copied (`engine:clone-bytes`, see
5699
+ * {@link SLOT_BYTES}). A test takes it before and after; never reset, so two
5700
+ * readers of it cannot disturb each other.
5701
+ */
5702
+ function engineWork() {
5703
+ return {
5704
+ applyPatch: applies,
5705
+ cloneBytes: clonedBytes
5706
+ };
5707
+ }
5708
+
5709
+ // #region the patch
5710
+
5711
+ /**
5712
+ * Applies one patch to a module's content. Nothing is mutated: the module
5713
+ * passed in is still what it was, and the one returned shares every subtree
5714
+ * the patch did not touch.
5715
+ *
5716
+ * `failed` means the patch does not apply, and then none of it does: half a
5717
+ * patch is half an edit. The caller stops the module's chain there.
5718
+ *
5719
+ * `schema` is the module's serialized schema: it says which ops reach into a
5720
+ * `.jsonValues()` entry. Without one, every op is an op on the source.
5721
+ */
5722
+ function applyPatchRecord(module, record, schema) {
5723
+ applies++;
5724
+ var patchId = record.patchId;
5725
+ var fail = function fail(message) {
5726
+ return {
5727
+ status: "failed",
5728
+ patchId: patchId,
5729
+ message: message
5730
+ };
5731
+ };
5732
+ var source = module.source;
5733
+ var entries = null;
5734
+ var stateOf = function stateOf(key) {
5735
+ var _get;
5736
+ return (_get = (entries !== null && entries !== void 0 ? entries : module.entries).get(key)) !== null && _get !== void 0 ? _get : implicitState(source, key);
5737
+ };
5738
+ var setEntry = function setEntry(key, state) {
5739
+ if (entries === null) {
5740
+ clonedBytes += SLOT_BYTES * module.entries.size;
5741
+ entries = new Map(module.entries);
5742
+ }
5743
+ entries.set(key, state);
5744
+ };
5745
+ /** Sets it, unless the entry already failed: an entry's error is sticky. */
5746
+ var settle = function settle(key, state) {
5747
+ var _stateOf;
5748
+ if (((_stateOf = stateOf(key)) === null || _stateOf === void 0 ? void 0 : _stateOf.kind) !== "error") setEntry(key, state);
5749
+ };
5750
+ /**
5751
+ * The record's keys as they stand while this patch is walked, for a write
5752
+ * of the whole record to expand against. Only the key set: the source holds
5753
+ * markers, and an entry's content may not be loaded. Seeded from the source
5754
+ * BEFORE this patch and kept current by the whole-entry ops below.
5755
+ */
5756
+ var keys = null;
5757
+ var currentKeys = function currentKeys() {
5758
+ if (keys === null) {
5759
+ keys = new Map();
5760
+ if (isObjectNode(module.source)) {
5761
+ for (var _i = 0, _Object$keys = Object.keys(module.source); _i < _Object$keys.length; _i++) {
5762
+ var _key = _Object$keys[_i];
5763
+ keys.set(_key, undefined);
5764
+ }
5765
+ }
5766
+ }
5767
+ return keys;
5768
+ };
5769
+ /** Where a file's `patch_id` lands in the source; last write wins its place. */
5770
+ var fileMarks = new Map();
5771
+ /** And in an entry's content. */
5772
+ var entryFileMarks = [];
5773
+ var _iterator = _createForOfIteratorHelper(record.patch),
5774
+ _step;
5775
+ try {
5776
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
5777
+ var rawOp = _step.value;
5778
+ /*
5779
+ * A write of the WHOLE record fans out into per-entry ops first, through
5780
+ * the expansion the commit flow uses too (`expandJsonValuesRootOp`).
5781
+ * Left as it is, it would put content where the source holds markers.
5782
+ */
5783
+ var ops = [rawOp];
5784
+ if (schema !== undefined && isJsonValuesRootOp(schema, rawOp)) {
5785
+ var expanded = expandJsonValuesRootOp(rawOp, currentKeys());
5786
+ if (result.isErr(expanded)) return fail(expanded.error.message);
5787
+ ops = expanded.value;
5788
+ }
5789
+ var _iterator3 = _createForOfIteratorHelper(ops),
5790
+ _step3;
5791
+ try {
5792
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
5793
+ var _op = _step3.value;
5794
+ var cls = schema === undefined ? {
5795
+ kind: "normal"
5796
+ } : classifyJsonValuesOp(schema, _op.path);
5797
+ if (_op.op === "file") {
5798
+ var _op$nestedFilePath;
5799
+ /*
5800
+ * A file op carries bytes, not content. What the content needs is
5801
+ * the `patch_id` saying those bytes are not published yet, so the
5802
+ * file is served from the patch rather than from a `/public` path
5803
+ * that holds nothing. Keyed by where it LANDS -- the field's path
5804
+ * plus `nestedFilePath` -- because a video's poster and captions are
5805
+ * file ops on the video's own path. A delete has no bytes to point at.
5806
+ */
5807
+ if (_op.value === null) continue;
5808
+ var nested = (_op$nestedFilePath = _op.nestedFilePath) !== null && _op$nestedFilePath !== void 0 ? _op$nestedFilePath : [];
5809
+ if (cls.kind === "entry" && cls.subPath.length > 0) {
5810
+ if (cls.recordPath.length === 0) {
5811
+ entryFileMarks.push({
5812
+ key: cls.entryKey,
5813
+ path: cls.subPath.concat(nested, "patch_id")
5814
+ });
5815
+ }
5816
+ continue;
5817
+ }
5818
+ var at = _op.path.concat(nested, "patch_id");
5819
+ fileMarks.set(JSON.stringify(at), at);
5820
+ if (cls.kind === "entry" && cls.recordPath.length === 0) {
5821
+ // The entry IS the media: its content gets the mark too.
5822
+ entryFileMarks.push({
5823
+ key: cls.entryKey,
5824
+ path: nested.concat("patch_id")
5825
+ });
5826
+ }
5827
+ continue;
5828
+ }
5829
+ if (cls.kind === "normal") {
5830
+ var _applied = applyOp(source, _op);
5831
+ if (result.isErr(_applied)) return fail(_applied.error.message);
5832
+ source = _applied.value;
5833
+ if (_op.op === "move") {
5834
+ var fromKey = wholeEntryKey(schema, _op.from);
5835
+ if (fromKey !== undefined) settle(fromKey, {
5836
+ kind: "deleted"
5837
+ });
5838
+ }
5839
+ if (_op.path.length === 0 && (schema === null || schema === void 0 ? void 0 : schema.type) === "record" && schema.jsonValues === true) {
5840
+ // The record itself was replaced (by something that is not a
5841
+ // record): every entry went with it.
5842
+ clonedBytes += SLOT_BYTES * (entries !== null && entries !== void 0 ? entries : module.entries).size;
5843
+ entries = new Map();
5844
+ }
5845
+ continue;
5846
+ }
5847
+ if (cls.recordPath.length > 0) {
5848
+ return fail("Nested .jsonValues() records are not supported: '".concat(cls.recordPath.join("."), "'. Use .jsonValues() only on a module's root record/router."));
5849
+ }
5850
+ var _key3 = cls.entryKey;
5851
+ if (cls.subPath.length > 0) {
5852
+ // An edit INSIDE an entry: its content, never the source's marker.
5853
+ if (_op.op === "move" || _op.op === "copy") {
5854
+ var fromCls = schema === undefined ? {
5855
+ kind: "normal"
5856
+ } : classifyJsonValuesOp(schema, _op.from);
5857
+ if (fromCls.kind !== "entry" || fromCls.entryKey !== _key3) {
5858
+ return fail("Cannot '".concat(_op.op, "' between different jsonValues entries"));
5859
+ }
5860
+ }
5861
+ var rebased = rebaseContentOp(_op, cls.recordPath.length + 1);
5862
+ if (result.isErr(rebased)) return fail(rebased.error.message);
5863
+ var _state = stateOf(_key3);
5864
+ if (_state === undefined || _state.kind === "deleted") {
5865
+ setEntry(_key3, {
5866
+ kind: "error",
5867
+ patchId: patchId,
5868
+ message: "Cannot edit jsonValues entry '".concat(_key3, "': it does not exist")
5869
+ });
5870
+ } else if (_state.kind === "waiting") {
5871
+ setEntry(_key3, {
5872
+ kind: "waiting",
5873
+ from: _state.from,
5874
+ ops: {
5875
+ op: rebased.value,
5876
+ patchId: patchId,
5877
+ before: _state.ops
5878
+ }
5879
+ });
5880
+ } else if (_state.kind === "content") {
5881
+ var _applied2 = applyOp(_state.content, rebased.value);
5882
+ setEntry(_key3, result.isErr(_applied2) ? {
5883
+ kind: "error",
5884
+ patchId: patchId,
5885
+ message: _applied2.error.message
5886
+ } : {
5887
+ kind: "content",
5888
+ content: _applied2.value
5889
+ });
5890
+ }
5891
+ continue;
5892
+ }
5893
+
5894
+ // A WHOLE entry: a marker in the source, content in the entry.
5895
+ var marker = _defineProperty(_defineProperty({}, VAL_EXTENSION, "json"), "patch_id", patchId);
5896
+ if (_op.op === "add" || _op.op === "replace") {
5897
+ var written = _op.op === "add" ? addAt(source, _op.path, marker) : replaceAt(source, _op.path, marker);
5898
+ if (result.isErr(written)) return fail(written.error.message);
5899
+ source = written.value;
5900
+ currentKeys().set(_key3, undefined);
5901
+ settle(_key3, {
5902
+ kind: "content",
5903
+ content: _op.value
5904
+ });
5905
+ } else if (_op.op === "remove") {
5906
+ var removed = removeAt(source, _op.path);
5907
+ if (result.isErr(removed)) return fail(removed.error.message);
5908
+ source = removed.value.doc;
5909
+ currentKeys()["delete"](_key3);
5910
+ settle(_key3, {
5911
+ kind: "deleted"
5912
+ });
5913
+ } else if (_op.op === "move" || _op.op === "copy") {
5914
+ var _fromCls = schema === undefined ? {
5915
+ kind: "normal"
5916
+ } : classifyJsonValuesOp(schema, _op.from);
5917
+ if (_fromCls.kind !== "entry" || _fromCls.subPath.length !== 0 || _fromCls.recordPath.length !== 0) {
5918
+ 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("."), "')"));
5919
+ }
5920
+ var _fromKey = _fromCls.entryKey;
5921
+ // Read before anything moves: the destination is the source entry
5922
+ // as it stood at this op.
5923
+ var fromState = stateOf(_fromKey);
5924
+ var added = addAt(source, _op.path, marker);
5925
+ if (result.isErr(added)) return fail(added.error.message);
5926
+ source = added.value;
5927
+ currentKeys().set(_key3, undefined);
5928
+ if (_op.op === "move") {
5929
+ var _removed = removeAt(source, _op.from);
5930
+ if (result.isErr(_removed)) return fail(_removed.error.message);
5931
+ source = _removed.value.doc;
5932
+ currentKeys()["delete"](_fromKey);
5933
+ }
5934
+ settle(_key3, fromState === undefined || fromState.kind === "deleted" ? {
5935
+ kind: "error",
5936
+ patchId: patchId,
5937
+ message: "Cannot resolve '".concat(_op.op, "' of jsonValues entry '").concat(_key3, "' from its own content")
5938
+ } : fromState);
5939
+ if (_op.op === "move" && _fromKey !== _key3) {
5940
+ settle(_fromKey, {
5941
+ kind: "deleted"
5942
+ });
5943
+ }
5944
+ } else {
5945
+ // `test` of a whole entry: the commit refuses it (`ValOps.prepare`),
5946
+ // so the read does too, rather than showing a patch publishing fails.
5947
+ return fail("Unsupported op '".concat(_op.op, "' on jsonValues entry '").concat(_key3, "' (supported: add, remove, replace, move, copy)"));
5948
+ }
5949
+ }
5950
+ } catch (err) {
5951
+ _iterator3.e(err);
5952
+ } finally {
5953
+ _iterator3.f();
5954
+ }
5955
+ }
5956
+ } catch (err) {
5957
+ _iterator.e(err);
5958
+ } finally {
5959
+ _iterator.f();
5960
+ }
5961
+ var _iterator2 = _createForOfIteratorHelper(fileMarks.values()),
5962
+ _step2;
5963
+ try {
5964
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
5965
+ var _at = _step2.value;
5966
+ var marked = addAt(source, _at, patchId);
5967
+ if (result.isErr(marked)) return fail(marked.error.message);
5968
+ source = marked.value;
5969
+ }
5970
+ } catch (err) {
5971
+ _iterator2.e(err);
5972
+ } finally {
5973
+ _iterator2.f();
5974
+ }
5975
+ for (var _i2 = 0, _entryFileMarks = entryFileMarks; _i2 < _entryFileMarks.length; _i2++) {
5976
+ var _entryFileMarks$_i = _entryFileMarks[_i2],
5977
+ _key2 = _entryFileMarks$_i.key,
5978
+ path = _entryFileMarks$_i.path;
5979
+ var state = stateOf(_key2);
5980
+ var op = {
5981
+ op: "add",
5982
+ path: path,
5983
+ value: patchId
5984
+ };
5985
+ if ((state === null || state === void 0 ? void 0 : state.kind) === "content") {
5986
+ var applied = applyOp(state.content, op);
5987
+ setEntry(_key2, result.isErr(applied) ? {
5988
+ kind: "error",
5989
+ patchId: patchId,
5990
+ message: applied.error.message
5991
+ } : {
5992
+ kind: "content",
5993
+ content: applied.value
5994
+ });
5995
+ } else if ((state === null || state === void 0 ? void 0 : state.kind) === "waiting") {
5996
+ setEntry(_key2, {
5997
+ kind: "waiting",
5998
+ from: state.from,
5999
+ ops: {
6000
+ op: op,
6001
+ patchId: patchId,
6002
+ before: state.ops
6003
+ }
6004
+ });
6005
+ }
6006
+ }
6007
+ return {
6008
+ status: "applied",
6009
+ module: {
6010
+ source: source,
6011
+ entries: entries !== null && entries !== void 0 ? entries : module.entries
6012
+ }
6013
+ };
6014
+ }
6015
+
6016
+ /**
6017
+ * A module's chain: every patch in order, stopping at the first that does
6018
+ * not apply. `applied` is how many did, so the chain the content is AT is
6019
+ * the first `applied` ids -- what its version names.
6020
+ */
6021
+ function applyChain(module, records, schema) {
6022
+ var current = module;
6023
+ var _iterator4 = _createForOfIteratorHelper(records.entries()),
6024
+ _step4;
6025
+ try {
6026
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
6027
+ var _step4$value = _slicedToArray(_step4.value, 2),
6028
+ index = _step4$value[0],
6029
+ record = _step4$value[1];
6030
+ var res = applyPatchRecord(current, record, schema);
6031
+ if (res.status === "failed") {
6032
+ return {
6033
+ module: current,
6034
+ applied: index,
6035
+ failed: {
6036
+ patchId: res.patchId,
6037
+ message: res.message
6038
+ }
6039
+ };
6040
+ }
6041
+ current = res.module;
6042
+ }
6043
+ } catch (err) {
6044
+ _iterator4.e(err);
6045
+ } finally {
6046
+ _iterator4.f();
6047
+ }
6048
+ return {
6049
+ module: current,
6050
+ applied: records.length,
6051
+ failed: null
6052
+ };
6053
+ }
6054
+
6055
+ // #region entries
6056
+
6057
+ /**
6058
+ * What an entry is in this module: what the chain made it, or -- for one the
6059
+ * chain never touched -- the base's, if the source has its marker.
6060
+ */
6061
+ function entryState(module, key) {
6062
+ var _module$entries$get;
6063
+ return (_module$entries$get = module.entries.get(key)) !== null && _module$entries$get !== void 0 ? _module$entries$get : implicitState(module.source, key);
6064
+ }
6065
+ function implicitState(source, key) {
6066
+ return isObjectNode(source) && Object.prototype.hasOwnProperty.call(source, key) && Internal.isJson(source[key]) ? {
6067
+ kind: "waiting",
6068
+ from: key,
6069
+ ops: null
6070
+ } : undefined;
6071
+ }
6072
+
6073
+ /** An entry's content, read. */
6074
+
6075
+ /**
6076
+ * An entry's content: what {@link entryState} says, with what it was waiting
6077
+ * for applied. `baseContentOf` is the BASE content of an entry by key (its
6078
+ * `*.val.json`, or what a save left there), `undefined` for one the base does
6079
+ * not have.
6080
+ */
6081
+ function resolveEntry(key, state, baseContentOf) {
6082
+ if (state === undefined || state.kind === "deleted") {
6083
+ return {
6084
+ kind: "missing"
6085
+ };
6086
+ }
6087
+ if (state.kind === "error") {
6088
+ return {
6089
+ kind: "error",
6090
+ message: state.message,
6091
+ patchId: state.patchId
6092
+ };
6093
+ }
6094
+ if (state.kind === "content") {
6095
+ return {
6096
+ kind: "content",
6097
+ content: state.content
6098
+ };
6099
+ }
6100
+ var held = [];
6101
+ for (var at = state.ops; at !== null; at = at.before) held.push(at);
6102
+ var content = baseContentOf(state.from);
6103
+ if (content === undefined) {
6104
+ var first = held[held.length - 1];
6105
+ return first === undefined ? {
6106
+ kind: "missing"
6107
+ } : {
6108
+ kind: "error",
6109
+ message: "Cannot edit jsonValues entry '".concat(key, "': it does not exist"),
6110
+ patchId: first.patchId
6111
+ };
6112
+ }
6113
+ for (var i = held.length - 1; i >= 0; i--) {
6114
+ var _held$i = held[i],
6115
+ op = _held$i.op,
6116
+ patchId = _held$i.patchId;
6117
+ var applied = applyOp(content, op);
6118
+ if (result.isErr(applied)) {
6119
+ return {
6120
+ kind: "error",
6121
+ message: applied.error.message,
6122
+ patchId: patchId
6123
+ };
6124
+ }
6125
+ content = applied.value;
6126
+ }
6127
+ return {
6128
+ kind: "content",
6129
+ content: content
6130
+ };
6131
+ }
6132
+
6133
+ // #region copy-on-write JSON ops
6134
+
6135
+ /*
6136
+ * RFC 6902 as `JSONOps` (in `@valbuild/core/patch`) applies it -- the same
6137
+ * results and the same error messages, which the conformance cases hold --
6138
+ * but without mutating anything: a container on an op's path is copied, and
6139
+ * everything else is shared.
6140
+ */
6141
+
6142
+ function isArrayNode(node) {
6143
+ return Array.isArray(node);
6144
+ }
6145
+ function isObjectNode(node) {
6146
+ return _typeof(node) === "object" && node !== null && !Array.isArray(node);
6147
+ }
6148
+ function hasOwn(node, key) {
6149
+ return Object.prototype.hasOwnProperty.call(node, key);
6150
+ }
6151
+ function patchError(message) {
6152
+ return result.err(new PatchError(message));
6153
+ }
6154
+ function copyArray(node) {
6155
+ clonedBytes += SLOT_BYTES * node.length;
6156
+ return node.slice();
6157
+ }
6158
+
6159
+ /**
6160
+ * A shallow copy, key by key. Not a spread, which was tried: a module's root
6161
+ * is a large object in dictionary mode, and V8 spreads one of forty thousand
6162
+ * keys at about half this speed (the 5 MB keystroke of
6163
+ * `scenarios/measure/keystroke.ts` went from 19 ms to 33).
6164
+ */
6165
+ function copyObject(node) {
6166
+ var copy = {};
6167
+ var slots = 0;
6168
+ for (var _i3 = 0, _Object$keys2 = Object.keys(node); _i3 < _Object$keys2.length; _i3++) {
6169
+ var _key4 = _Object$keys2[_i3];
6170
+ setOwn(copy, _key4, node[_key4]);
6171
+ slots++;
6172
+ }
6173
+ clonedBytes += SLOT_BYTES * slots;
6174
+ return copy;
6175
+ }
6176
+
6177
+ /** An own property, even for `__proto__`, which `=` would not create. */
6178
+ function setOwn(target, key, value) {
6179
+ if (key === "__proto__") {
6180
+ Object.defineProperty(target, key, {
6181
+ value: value,
6182
+ writable: true,
6183
+ enumerable: true,
6184
+ configurable: true
6185
+ });
6186
+ } else {
6187
+ target[key] = value;
6188
+ }
6189
+ }
6190
+ function arrayIndex(key, node, inclusive) {
6191
+ if (inclusive && key === "-") return result.ok(node.length);
6192
+ var index = parseAndValidateArrayIndex(key);
6193
+ if (result.isErr(index)) return index;
6194
+ return (inclusive ? index.value <= node.length : index.value < node.length) ? index : patchError("Array index out of bounds");
6195
+ }
6196
+ function childOf(node, key) {
6197
+ if (isArrayNode(node)) {
6198
+ var index = parseAndValidateArrayIndex(key);
6199
+ if (result.isErr(index)) return index;
6200
+ return index.value >= node.length ? patchError("Array index out of bounds") : result.ok(node[index.value]);
6201
+ }
6202
+ if (isObjectNode(node)) {
6203
+ return result.ok(hasOwn(node, key) ? node[key] : undefined);
6204
+ }
6205
+ return patchError("Cannot access non-object/array");
6206
+ }
6207
+
6208
+ /** `node` with `key` set to `value`: the one copy a step on a path costs. */
6209
+ function withChild(node, key, value) {
6210
+ if (isArrayNode(node)) {
6211
+ var copy = copyArray(node);
6212
+ copy[Number(key)] = value;
6213
+ return copy;
6214
+ }
6215
+ if (isObjectNode(node)) {
6216
+ var _copy = copyObject(node);
6217
+ setOwn(_copy, key, value);
6218
+ return _copy;
6219
+ }
6220
+ return node;
6221
+ }
6222
+
6223
+ /**
6224
+ * Walks `path` to the parent of its last segment, lets `leaf` change that
6225
+ * parent, and copies every container on the way back up. Errors as
6226
+ * `JSONOps`'s pointer walk: the first missing step is reported.
6227
+ */
6228
+ function updateAt(node, path, index, leaf) {
6229
+ var key = path[index];
6230
+ if (index === path.length - 1) return leaf(node, key);
6231
+ var child = childOf(node, key);
6232
+ if (result.isErr(child)) return child;
6233
+ if (child.value === undefined) {
6234
+ return patchError("Path refers to non-existing object/array");
6235
+ }
6236
+ var updated = updateAt(child.value, path, index + 1, leaf);
6237
+ if (result.isErr(updated)) return updated;
6238
+ return result.ok({
6239
+ node: updated.value.node === child.value ? node : withChild(node, key, updated.value.node),
6240
+ out: updated.value.out
6241
+ });
6242
+ }
6243
+ function isNonEmpty(path) {
6244
+ return path.length > 0;
6245
+ }
6246
+ function getAt(doc, path) {
6247
+ var node = doc;
6248
+ var _iterator5 = _createForOfIteratorHelper(path),
6249
+ _step5;
6250
+ try {
6251
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
6252
+ var _key5 = _step5.value;
6253
+ var child = childOf(node, _key5);
6254
+ if (result.isErr(child)) return child;
6255
+ if (child.value === undefined) {
6256
+ return patchError("Path refers to non-existing object/array");
6257
+ }
6258
+ node = child.value;
6259
+ }
6260
+ } catch (err) {
6261
+ _iterator5.e(err);
6262
+ } finally {
6263
+ _iterator5.f();
6264
+ }
6265
+ return result.ok(node);
6266
+ }
6267
+ function addAt(doc, path, value) {
6268
+ if (!isNonEmpty(path)) return result.ok(value);
6269
+ var res = updateAt(doc, path, 0, function (parent, key) {
6270
+ if (isArrayNode(parent)) {
6271
+ var index = arrayIndex(key, parent, true);
6272
+ if (result.isErr(index)) return index;
6273
+ var copy = copyArray(parent);
6274
+ copy.splice(index.value, 0, value);
6275
+ return result.ok({
6276
+ node: copy,
6277
+ out: null
6278
+ });
6279
+ }
6280
+ if (isObjectNode(parent)) {
6281
+ var _copy2 = copyObject(parent);
6282
+ setOwn(_copy2, key, value);
6283
+ return result.ok({
6284
+ node: _copy2,
6285
+ out: null
6286
+ });
6287
+ }
6288
+ return patchError("Cannot add to non-object/array");
6289
+ });
6290
+ return result.isErr(res) ? res : result.ok(res.value.node);
6291
+ }
6292
+ function replaceAt(doc, path, value) {
6293
+ if (!isNonEmpty(path)) return result.ok(value);
6294
+ var res = updateAt(doc, path, 0, function (parent, key) {
6295
+ if (isArrayNode(parent)) {
6296
+ var index = arrayIndex(key, parent, false);
6297
+ if (result.isErr(index)) return index;
6298
+ var copy = copyArray(parent);
6299
+ copy[index.value] = value;
6300
+ return result.ok({
6301
+ node: copy,
6302
+ out: null
6303
+ });
6304
+ }
6305
+ if (isObjectNode(parent)) {
6306
+ if (!hasOwn(parent, key)) {
6307
+ return patchError("Cannot replace object element which does not exist");
6308
+ }
6309
+ var _copy3 = copyObject(parent);
6310
+ setOwn(_copy3, key, value);
6311
+ return result.ok({
6312
+ node: _copy3,
6313
+ out: null
6314
+ });
6315
+ }
6316
+ return patchError("Cannot replace in non-object/array");
6317
+ });
6318
+ return result.isErr(res) ? res : result.ok(res.value.node);
6319
+ }
6320
+ function removeAt(doc, path) {
6321
+ if (!isNonEmpty(path)) {
6322
+ return patchError("Cannot remove from non-object/array");
6323
+ }
6324
+ var res = updateAt(doc, path, 0, function (parent, key) {
6325
+ if (isArrayNode(parent)) {
6326
+ var index = arrayIndex(key, parent, false);
6327
+ if (result.isErr(index)) return index;
6328
+ var copy = copyArray(parent);
6329
+ var _copy$splice = copy.splice(index.value, 1),
6330
+ _copy$splice2 = _slicedToArray(_copy$splice, 1),
6331
+ removed = _copy$splice2[0];
6332
+ return result.ok({
6333
+ node: copy,
6334
+ out: removed
6335
+ });
6336
+ }
6337
+ if (isObjectNode(parent) && hasOwn(parent, key)) {
6338
+ var _copy4 = copyObject(parent);
6339
+ var _removed2 = parent[key];
6340
+ delete _copy4[key];
6341
+ return result.ok({
6342
+ node: _copy4,
6343
+ out: _removed2
6344
+ });
6345
+ }
6346
+ return patchError("Cannot remove from non-object/array");
6347
+ });
6348
+ return result.isErr(res) ? res : result.ok({
6349
+ doc: res.value.node,
6350
+ removed: res.value.out
6351
+ });
6352
+ }
6353
+
6354
+ /** One op on a document. `file` is not an op on content: the caller marks. */
6355
+ function applyOp(doc, op) {
6356
+ switch (op.op) {
6357
+ case "add":
6358
+ return addAt(doc, op.path, op.value);
6359
+ case "replace":
6360
+ return replaceAt(doc, op.path, op.value);
6361
+ case "remove":
6362
+ {
6363
+ var removed = removeAt(doc, op.path);
6364
+ return result.isErr(removed) ? removed : result.ok(removed.value.doc);
6365
+ }
6366
+ case "move":
6367
+ {
6368
+ var _removed3 = removeAt(doc, op.from);
6369
+ if (result.isErr(_removed3)) return _removed3;
6370
+ return addAt(_removed3.value.doc, op.path, _removed3.value.removed);
6371
+ }
6372
+ case "copy":
6373
+ {
6374
+ // Shared, not cloned: nothing here mutates, so two places can hold one
6375
+ // value.
6376
+ var value = getAt(doc, op.from);
6377
+ if (result.isErr(value)) return value;
6378
+ return addAt(doc, op.path, value.value);
6379
+ }
6380
+ case "test":
6381
+ {
6382
+ // As `applyPatch` in `@valbuild/core/patch`, which the commit applies
6383
+ // `.val.ts` text through: a path that does not exist, or a value there
6384
+ // that is not deeply equal, fails the patch (RFC 6902, 4.6). The two
6385
+ // have to agree, or a draft shows a patch Publish refuses.
6386
+ var _value = getAt(doc, op.path);
6387
+ if (result.isErr(_value)) return _value;
6388
+ if (!deepEqual(op.value, _value.value)) return patchError("Test failed");
6389
+ return result.ok(doc);
6390
+ }
6391
+ case "file":
6392
+ return patchError("Cannot apply a file patch here");
6393
+ }
6394
+ }
6395
+
6396
+ /*
6397
+ * The store: modules by address. See `docs/plans/one-content-model.md`,
6398
+ * "The store: one address, immutable values, tiers".
6399
+ *
6400
+ * Content at a version never changes, so a store of modules by `moduleKey`
6401
+ * needs no invalidation, only eviction. A base is a version with the empty
6402
+ * chain, so the same store holds both. Every place that keeps modules uses
6403
+ * the same tiers, in the order that fits it: Val's server keeps
6404
+ * `[memoryTier, bundleTier, …]` in front of its head (`ValHead` in
6405
+ * `@valbuild/server`), and the Studio's will put an origin tier behind them
6406
+ * (phase 7).
6407
+ */
6408
+
6409
+ /** One module's content at one version, as a tier holds it. */
6410
+
6411
+ // #region counted work
6412
+
6413
+ var tierHits = new Map();
6414
+ var tierMisses = 0;
6415
+
6416
+ /**
6417
+ * What the stores in this process have answered, by tier, and how often
6418
+ * none of them had a key: a test takes it before and after. Never reset.
6419
+ */
6420
+ function storeWork() {
6421
+ return {
6422
+ hits: Object.fromEntries(tierHits),
6423
+ misses: tierMisses
6424
+ };
6425
+ }
6426
+
6427
+ // #region tiered
6428
+
6429
+ /**
6430
+ * Asks each tier in turn; what a slower tier answers is written to the faster
6431
+ * ones before it. A tier that answers `null` has no say.
6432
+ */
6433
+ function tiered(tiers) {
6434
+ return {
6435
+ name: tiers.map(function (tier) {
6436
+ return tier.name;
6437
+ }).join(">"),
6438
+ get: function get(key) {
6439
+ return _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
6440
+ var _iterator, _step, _tierHits$get, _step$value, index, tier, found, _iterator2, _step2, _faster$put, faster, _t;
6441
+ return _regenerator().w(function (_context) {
6442
+ while (1) switch (_context.p = _context.n) {
6443
+ case 0:
6444
+ _iterator = _createForOfIteratorHelper(tiers.entries());
6445
+ _context.p = 1;
6446
+ _iterator.s();
6447
+ case 2:
6448
+ if ((_step = _iterator.n()).done) {
6449
+ _context.n = 6;
6450
+ break;
6451
+ }
6452
+ _step$value = _slicedToArray(_step.value, 2), index = _step$value[0], tier = _step$value[1];
6453
+ _context.n = 3;
6454
+ return tier.get(key);
6455
+ case 3:
6456
+ found = _context.v;
6457
+ if (!(found === null)) {
6458
+ _context.n = 4;
6459
+ break;
6460
+ }
6461
+ return _context.a(3, 5);
6462
+ case 4:
6463
+ tierHits.set(tier.name, ((_tierHits$get = tierHits.get(tier.name)) !== null && _tierHits$get !== void 0 ? _tierHits$get : 0) + 1);
6464
+ _iterator2 = _createForOfIteratorHelper(tiers.slice(0, index));
6465
+ try {
6466
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
6467
+ faster = _step2.value;
6468
+ (_faster$put = faster.put) === null || _faster$put === void 0 || _faster$put.call(faster, key, found, found.bytes);
6469
+ }
6470
+ } catch (err) {
6471
+ _iterator2.e(err);
6472
+ } finally {
6473
+ _iterator2.f();
6474
+ }
6475
+ return _context.a(2, found);
6476
+ case 5:
6477
+ _context.n = 2;
6478
+ break;
6479
+ case 6:
6480
+ _context.n = 8;
6481
+ break;
6482
+ case 7:
6483
+ _context.p = 7;
6484
+ _t = _context.v;
6485
+ _iterator.e(_t);
6486
+ case 8:
6487
+ _context.p = 8;
6488
+ _iterator.f();
6489
+ return _context.f(8);
6490
+ case 9:
6491
+ tierMisses++;
6492
+ return _context.a(2, null);
6493
+ }
6494
+ }, _callee, null, [[1, 7, 8, 9]]);
6495
+ }))();
6496
+ },
6497
+ put: function put(key, value, bytes) {
6498
+ var _tiers$, _tiers$$put;
6499
+ (_tiers$ = tiers[0]) === null || _tiers$ === void 0 || (_tiers$$put = _tiers$.put) === null || _tiers$$put === void 0 || _tiers$$put.call(_tiers$, key, value, bytes);
6500
+ }
6501
+ };
6502
+ }
6503
+
6504
+ // #region memory
6505
+
6506
+ /**
6507
+ * Bounded by bytes; keeps the last `lastPerModule` versions per module path
6508
+ * (and, apart from them, the last `lastPerModule` bases), and lets go of the least recently read value when over `budgetBytes`. A
6509
+ * value bigger than the whole budget is still held while it is the newest,
6510
+ * so the read that made it is answered; the next put lets it go.
6511
+ */
6512
+ function memoryTier(opts) {
6513
+ var _opts$name;
6514
+ /** In least-recently-read order: a read moves a key to the end. */
6515
+ var held = new Map();
6516
+ /** Per module, its keys oldest first. */
6517
+ var rings = new Map();
6518
+ var bytes = 0;
6519
+ var evicted = 0;
6520
+ var versionOf = function versionOf(key) {
6521
+ var _held$get;
6522
+ return (_held$get = held.get(key)) === null || _held$get === void 0 ? void 0 : _held$get.value.version;
6523
+ };
6524
+ var drop = function drop(key) {
6525
+ var entry = held.get(key);
6526
+ if (entry === undefined) return;
6527
+ held["delete"](key);
6528
+ bytes -= entry.bytes;
6529
+ evicted++;
6530
+ var ring = rings.get(entry.value.path);
6531
+ if (ring !== undefined) {
6532
+ var at = ring.indexOf(key);
6533
+ if (at >= 0) ring.splice(at, 1);
6534
+ if (ring.length === 0) rings["delete"](entry.value.path);
6535
+ }
6536
+ };
6537
+ return {
6538
+ name: (_opts$name = opts.name) !== null && _opts$name !== void 0 ? _opts$name : "memory",
6539
+ get: function get(key) {
6540
+ var entry = held.get(key);
6541
+ if (entry === undefined) return null;
6542
+ held["delete"](key);
6543
+ held.set(key, entry);
6544
+ return entry.value;
6545
+ },
6546
+ put: function put(key, value, valueBytes) {
6547
+ var _rings$get;
6548
+ if (held.has(key)) {
6549
+ drop(key);
6550
+ evicted--;
6551
+ }
6552
+ held.set(key, {
6553
+ value: value,
6554
+ bytes: valueBytes
6555
+ });
6556
+ bytes += valueBytes;
6557
+ var ring = (_rings$get = rings.get(value.path)) !== null && _rings$get !== void 0 ? _rings$get : [];
6558
+ ring.push(key);
6559
+ rings.set(value.path, ring);
6560
+ // The last N versions, and apart from them the last N bases: a base
6561
+ // is what a slower tier answered (the bundle's own object, held
6562
+ // anyway) and written up here, and it must not push out the version
6563
+ // before the newest -- which a reader of the previous version still
6564
+ // asks for.
6565
+ var isBase = value.version.chain === EMPTY_CHAIN;
6566
+ var sameKind = ring.filter(function (held) {
6567
+ var _versionOf;
6568
+ return ((_versionOf = versionOf(held)) === null || _versionOf === void 0 ? void 0 : _versionOf.chain) === EMPTY_CHAIN === isBase;
6569
+ });
6570
+ var _iterator3 = _createForOfIteratorHelper(sameKind.slice(0, Math.max(0, sameKind.length - opts.lastPerModule))),
6571
+ _step3;
6572
+ try {
6573
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
6574
+ var oldest = _step3.value;
6575
+ drop(oldest);
6576
+ }
6577
+ } catch (err) {
6578
+ _iterator3.e(err);
6579
+ } finally {
6580
+ _iterator3.f();
6581
+ }
6582
+ var _iterator4 = _createForOfIteratorHelper(held.keys()),
6583
+ _step4;
6584
+ try {
6585
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
6586
+ var lru = _step4.value;
6587
+ if (bytes <= opts.budgetBytes) break;
6588
+ if (lru === key) continue;
6589
+ drop(lru);
6590
+ }
6591
+ } catch (err) {
6592
+ _iterator4.e(err);
6593
+ } finally {
6594
+ _iterator4.f();
6595
+ }
6596
+ },
6597
+ charge: function charge(key, extra) {
6598
+ var entry = held.get(key);
6599
+ if (entry === undefined) return;
6600
+ entry.bytes += extra;
6601
+ bytes += extra;
6602
+ },
6603
+ latest: function latest(path) {
6604
+ var _held$get$value, _held$get2;
6605
+ var ring = rings.get(path);
6606
+ var newest = ring === null || ring === void 0 ? void 0 : ring[ring.length - 1];
6607
+ 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;
6608
+ },
6609
+ stats: function stats() {
6610
+ // Versions: what a chain made. A base written up from a slower tier is
6611
+ // the build's own object (counted at no bytes), held beside them, and
6612
+ // not one of the module's versions.
6613
+ var mostVersions = 0;
6614
+ var mostBytes = 0;
6615
+ var versions = 0;
6616
+ var modules = 0;
6617
+ var _iterator5 = _createForOfIteratorHelper(rings.values()),
6618
+ _step5;
6619
+ try {
6620
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
6621
+ var ring = _step5.value;
6622
+ var made = ring.filter(function (key) {
6623
+ var _versionOf2;
6624
+ return ((_versionOf2 = versionOf(key)) === null || _versionOf2 === void 0 ? void 0 : _versionOf2.chain) !== EMPTY_CHAIN;
6625
+ });
6626
+ versions += made.length;
6627
+ if (made.length > 0) modules++;
6628
+ mostVersions = Math.max(mostVersions, made.length);
6629
+ var moduleBytes = 0;
6630
+ var _iterator6 = _createForOfIteratorHelper(ring),
6631
+ _step6;
6632
+ try {
6633
+ for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
6634
+ var _held$get$bytes, _held$get3;
6635
+ var _key = _step6.value;
6636
+ 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;
6637
+ }
6638
+ } catch (err) {
6639
+ _iterator6.e(err);
6640
+ } finally {
6641
+ _iterator6.f();
6642
+ }
6643
+ mostBytes = Math.max(mostBytes, moduleBytes);
6644
+ }
6645
+ } catch (err) {
6646
+ _iterator5.e(err);
6647
+ } finally {
6648
+ _iterator5.f();
6649
+ }
6650
+ return {
6651
+ bytes: bytes,
6652
+ versions: versions,
6653
+ modules: modules,
6654
+ mostVersionsOfOneModule: mostVersions,
6655
+ mostBytesOfOneModule: mostBytes,
6656
+ evicted: evicted
6657
+ };
6658
+ }
6659
+ };
6660
+ }
6661
+
6662
+ // #region bundle
6663
+
6664
+ /**
6665
+ * The build's own modules, free: answers only a key whose version is the
6666
+ * bundle's -- its schema, its base and the empty chain -- with the bundle's
6667
+ * own object, which the process holds for every visitor anyway. `bundle`
6668
+ * says what the build evaluated each module to; `null` for a module the
6669
+ * build does not have.
6670
+ */
6671
+ function bundleTier(bundle) {
6672
+ var name = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "bundle";
6673
+ return {
6674
+ name: name,
6675
+ get: function get(key) {
6676
+ return _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
6677
+ var path, own, version;
6678
+ return _regenerator().w(function (_context2) {
6679
+ while (1) switch (_context2.n) {
6680
+ case 0:
6681
+ path = pathOfModuleKey(key);
6682
+ if (!(path === null)) {
6683
+ _context2.n = 1;
6684
+ break;
6685
+ }
6686
+ return _context2.a(2, null);
6687
+ case 1:
6688
+ _context2.n = 2;
6689
+ return bundle(path);
6690
+ case 2:
6691
+ own = _context2.v;
6692
+ if (!(own === null)) {
6693
+ _context2.n = 3;
6694
+ break;
6695
+ }
6696
+ return _context2.a(2, null);
6697
+ case 3:
6698
+ version = _objectSpread2(_objectSpread2({}, own.version), {}, {
6699
+ chain: EMPTY_CHAIN
6700
+ });
6701
+ if (!(moduleKey(path, version) !== key)) {
6702
+ _context2.n = 4;
6703
+ break;
6704
+ }
6705
+ return _context2.a(2, null);
6706
+ case 4:
6707
+ return _context2.a(2, {
6708
+ path: path,
6709
+ version: version,
6710
+ content: baseModuleContent(own.source),
6711
+ patches: [],
6712
+ bytes: 0
6713
+ });
6714
+ }
6715
+ }, _callee2);
6716
+ }))();
6717
+ }
6718
+ };
6719
+ }
6720
+
6721
+ /** The module a {@link moduleKey} is the address of, or `null` for none. */
6722
+ function pathOfModuleKey(key) {
6723
+ var at = key.lastIndexOf("@");
6724
+ return at <= 0 ? null : key.slice(0, at);
6725
+ }
6726
+
6727
+ /**
6728
+ * The version a {@link moduleKey} names, or `null` when it is not one. The
6729
+ * three parts are hex or empty, so the split is unambiguous.
6730
+ */
6731
+ function parseModuleKey(key) {
6732
+ var path = pathOfModuleKey(key);
6733
+ if (path === null) return null;
6734
+ var parts = key.slice(path.length + 1).split(".");
6735
+ var _parts = _slicedToArray(parts, 3),
6736
+ schema = _parts[0],
6737
+ base = _parts[1],
6738
+ chain = _parts[2];
6739
+ if (parts.length !== 3 || schema === undefined || base === undefined || chain === undefined) {
6740
+ return null;
6741
+ }
6742
+ return {
6743
+ path: path,
6744
+ version: {
6745
+ schema: schema,
6746
+ base: base,
6747
+ chain: chain
6748
+ }
6749
+ };
6750
+ }
6751
+
6752
+ // #region entries
6753
+
6754
+ /**
6755
+ * A `.jsonValues()` entry's version: the record's schema and base, the
6756
+ * entry's key, and the chain of the record's patches that touched THIS
6757
+ * entry -- so an entry no patch touched keeps its version while the record's
6758
+ * chain grows, and two reads at one entry version are the same content.
6759
+ * Entries are stored on their own, so a large record is not copied because
6760
+ * one entry changed.
6761
+ */
6762
+
6763
+ /** The address of an entry's content. Never changes meaning. */
6764
+ function entryAddress(path, v) {
6765
+ return "".concat(moduleKey(path, v), "#").concat(v.key);
6766
+ }
6767
+
6768
+ /** Whether two entry versions name the same content. */
6769
+ function sameEntryVersion(a, b) {
6770
+ return a.key === b.key && a.schema === b.schema && a.base === b.base && a.chain === b.chain;
6771
+ }
6772
+
6773
+ var SharedValConfig = z.object({
6774
+ contentHostUrl: z.string().optional(),
6775
+ appHostUrl: z.string().optional(),
6776
+ project: z.string().optional(),
4753
6777
  root: z.string().optional(),
4754
6778
  gitCommit: z.string().optional(),
4755
6779
  gitBranch: z.string().optional(),
@@ -6230,24 +8254,30 @@ function emptyRecord(schema, context) {
6230
8254
  }));
6231
8255
  }
6232
8256
 
6233
- var toolNames = ["get_all_schema", "get_source", "search_content", "validate_content", "create_patch", "add_session_image_to_gallery", "remove_image_gallery_entry", "navigate_to", "get_current_context", "get_patches", "get_source_path_from_route", "set_session_name", "show_compare_view", "ask_user_question", "duplicate_source", "empty_at_path", "count_entries", "get_record_keys", "list_sketchpads", "create_sketchpad", "read_sketchpad", "write_sketchpad"];
8257
+ var toolNames = ["get_all_schema", "get_source", "search_content", "validate_content", "create_patch", "add_session_image_to_gallery", "remove_image_gallery_entry", "convert_session_image", "navigate_to", "get_current_context", "get_patches", "get_source_path_from_route", "set_session_name", "show_compare_view", "ask_user_question", "duplicate_source", "empty_at_path", "count_entries", "get_record_keys", "list_sketchpads", "create_sketchpad", "read_sketchpad", "write_sketchpad", "highlight_sketchpad_blocks", "clear_sketchpad_highlights"];
6234
8258
 
6235
8259
  /**
6236
8260
  * A page sketch: a page written as indented text, with four primitives.
6237
8261
  *
6238
8262
  * ```
8263
+ * --- /about a route: what follows is the page at /about
6239
8264
  * # Our people text (a heading: #, ## or ###)
6240
8265
  * We are 40 people in Oslo. text (a paragraph)
8266
+ * ## Hi {field: /x.val.ts?p="t"} text linked to a content field: typing
8267
+ * in it writes the field
6241
8268
  * [image: Team photo] placeholder: [kind] or [kind: label]
6242
8269
  * [hero: Roof](image: /a.jpg) a placeholder with an image (or video) in it
6243
8270
  * icon hero M3 5h18v14H3z how every [hero] box is drawn: SVG path data
6244
8271
  * row its children side by side
6245
- * col 2 its children stacked; 2 shares of the row
6246
- * ## Design
8272
+ * col its children stacked; columns share a row
8273
+ * ## Design equally
6247
8274
  * col
6248
8275
  * [button: Join us]
6249
8276
  * ```
6250
8277
  *
8278
+ * A sketchpad is not one page's: a route divider starts the part about a
8279
+ * page, so one sketch can cover a page and the two it links to.
8280
+ *
6251
8281
  * It is a scratch pad, not a layout language: it says WHAT is on a page and
6252
8282
  * roughly WHERE, so an assistant and a person can agree on a page's content
6253
8283
  * before anyone worries about how it looks. The person never sees this text,
@@ -6266,7 +8296,21 @@ var toolNames = ["get_all_schema", "get_source", "search_content", "validate_con
6266
8296
  * that builds the page reads it exactly as the person left it.
6267
8297
  */
6268
8298
 
8299
+ /** The start of the part about one page. Only at the top level. */
8300
+
8301
+ /** Columns share their row equally: a sketch says what, not how wide. */
8302
+
8303
+ /**
8304
+ * `src` names a file the project has, never a guess: its `path` exactly as
8305
+ * the content has it (`/public/val/hero.webp`, or its remote URL), or an
8306
+ * image uploaded to the assistant's session, `session:<key>`. Which files
8307
+ * those are is the Studio's to check; the text only holds the name.
8308
+ */
8309
+
8310
+ /** The prefix of a `src` that is an image in the assistant's session. */
8311
+ var SESSION_MEDIA_PREFIX = "session:";
6269
8312
  var CONTAINER = /^(row|col)(?:\s+(\d+))?$/;
8313
+ var ROUTE = /^---(?:\s+(.*))?$/;
6270
8314
  var PLACEHOLDER = /^\[([^\]:]*)(?::([^\]]*))?\](?:\((image|video):\s*([^\s)]+)\s*\))?$/;
6271
8315
  var HAS_MEDIA = /^\[[^\]]*\]\(/;
6272
8316
  var ICON = /^icon\s+(\S+)\s+([Mm][\d\s.-].*)$/;
@@ -6309,6 +8353,32 @@ function parseSketch(source) {
6309
8353
  while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
6310
8354
  stack.pop();
6311
8355
  }
8356
+ var route = ROUTE.exec(content);
8357
+ if (route) {
8358
+ var _route$;
8359
+ var _path = ((_route$ = route[1]) !== null && _route$ !== void 0 ? _route$ : "").trim();
8360
+ if (!_path.startsWith("/") || /\s/.test(_path)) {
8361
+ errors.push({
8362
+ line: line,
8363
+ message: 'A route divider names one page by its route, like "--- /about".'
8364
+ });
8365
+ }
8366
+ if (indent > 0) {
8367
+ errors.push({
8368
+ line: line,
8369
+ message: "A route divider starts a page, so it goes at the top level, not indented."
8370
+ });
8371
+ }
8372
+ // It closes everything open: what follows is another page.
8373
+ stack.length = 1;
8374
+ stack[0].lastIndent = 0;
8375
+ blocks.push({
8376
+ type: "route",
8377
+ line: line,
8378
+ route: _path
8379
+ });
8380
+ return;
8381
+ }
6312
8382
  var parent = stack[stack.length - 1];
6313
8383
  var node = parseContent(content, line, errors);
6314
8384
 
@@ -6350,7 +8420,27 @@ function parseSketch(source) {
6350
8420
  errors: errors
6351
8421
  };
6352
8422
  }
6353
- function parseContent(content, line, errors) {
8423
+
8424
+ /** The link to a content field at the end of a text line. */
8425
+ var FIELD = / \{field: (\/[^{}]*)\}$/;
8426
+ function parseContent(whole, line, errors) {
8427
+ var link = FIELD.exec(whole);
8428
+ if (link) {
8429
+ var node = parseLine(whole.slice(0, link.index), line, errors);
8430
+ if (node.type !== "text") {
8431
+ errors.push({
8432
+ line: line,
8433
+ message: "Only a heading or a paragraph is linked to a field: put {field: ...} at the end of one."
8434
+ });
8435
+ return node;
8436
+ }
8437
+ return _objectSpread2(_objectSpread2({}, node), {}, {
8438
+ field: link[1].trim()
8439
+ });
8440
+ }
8441
+ return parseLine(whole, line, errors);
8442
+ }
8443
+ function parseLine(content, line, errors) {
6354
8444
  // A leading backslash keeps a line that looks like a keyword as text.
6355
8445
  if (content.startsWith("\\")) {
6356
8446
  return {
@@ -6375,17 +8465,15 @@ function parseContent(content, line, errors) {
6375
8465
  children: []
6376
8466
  };
6377
8467
  }
6378
- var weight = container[2] === undefined ? 1 : Number(container[2]);
6379
- if (weight === 0) {
8468
+ if (container[2] !== undefined) {
6380
8469
  errors.push({
6381
8470
  line: line,
6382
- message: "A col needs a weight of 1 or more."
8471
+ message: 'Columns share a row equally, so a col takes no number: write "col".'
6383
8472
  });
6384
8473
  }
6385
8474
  return {
6386
8475
  type: "col",
6387
8476
  line: line,
6388
- weight: Math.max(weight, 1),
6389
8477
  children: []
6390
8478
  };
6391
8479
  }
@@ -6459,13 +8547,19 @@ function parseContent(content, line, errors) {
6459
8547
  * The line a text block is written as. Line breaks become spaces, and a
6460
8548
  * paragraph that would read as something else is kept as text with `\`.
6461
8549
  */
6462
- function formatSketchText(level, text) {
8550
+ function formatSketchText(level, text, field) {
6463
8551
  var flat = text.replace(/\s+/g, " ").trim();
8552
+ var path = field === null || field === void 0 ? void 0 : field.replace(/\s+/g, " ").trim();
8553
+ // A source path, and one the line can be read back with: no braces.
8554
+ var link = path && /^\/[^{}]*$/.test(path) ? " {field: ".concat(path, "}") : "";
6464
8555
  if (level > 0) {
6465
- return "#".repeat(level) + " " + flat;
8556
+ return "#".repeat(level) + " " + flat + link;
6466
8557
  }
6467
- var ambiguous = flat.startsWith("\\") || flat.startsWith("[") || CONTAINER.test(flat) || HEADING.test(flat) || ICON.test(flat);
6468
- return ambiguous ? "\\" + flat : flat;
8558
+ // Read as the whole line will be, link and all: `icon x M` is text, and
8559
+ // `icon x M {field: ...}` would be an icon.
8560
+ var whole = flat + link;
8561
+ var ambiguous = flat.startsWith("\\") || flat.startsWith("[") || CONTAINER.test(whole) || ROUTE.test(whole) || HEADING.test(whole) || ICON.test(whole);
8562
+ return (ambiguous ? "\\" + flat : flat) + link;
6469
8563
  }
6470
8564
 
6471
8565
  /** The line a placeholder is written as. */
@@ -6490,17 +8584,26 @@ function formatSketchBox(placeholder) {
6490
8584
  */
6491
8585
  function formatSketch(blocks, icons) {
6492
8586
  var lines = [];
8587
+ // One block per field: the first keeps the link.
8588
+ var linked = new Set();
6493
8589
  var _walk = function walk(node, depth, inRow) {
6494
8590
  var indent = " ".repeat(depth);
6495
8591
  switch (node.type) {
6496
8592
  case "text":
6497
8593
  if (node.text) {
6498
- lines.push(indent + formatSketchText(node.level, node.text));
8594
+ var field = node.field && !linked.has(node.field) ? node.field : undefined;
8595
+ if (field) {
8596
+ linked.add(field);
8597
+ }
8598
+ lines.push(indent + formatSketchText(node.level, node.text, field));
6499
8599
  }
6500
8600
  return;
6501
8601
  case "placeholder":
6502
8602
  lines.push(indent + formatSketchBox(node));
6503
8603
  return;
8604
+ case "route":
8605
+ lines.push("--- ".concat(node.route));
8606
+ return;
6504
8607
  case "row":
6505
8608
  lines.push(indent + "row");
6506
8609
  if (node.children.length === 0) {
@@ -6517,7 +8620,7 @@ function formatSketch(blocks, icons) {
6517
8620
  });
6518
8621
  return;
6519
8622
  }
6520
- lines.push(indent + (node.weight === 1 ? "col" : "col ".concat(node.weight)));
8623
+ lines.push(indent + "col");
6521
8624
  node.children.forEach(function (child) {
6522
8625
  return _walk(child, depth + 1, false);
6523
8626
  });
@@ -6537,6 +8640,33 @@ function formatSketch(blocks, icons) {
6537
8640
  return all.length > 0 ? all.join("\n") + "\n" : "";
6538
8641
  }
6539
8642
 
8643
+ /**
8644
+ * The text of every block linked to a field, by field: what the Studio
8645
+ * writes to the content when a linked block changes. The first block wins,
8646
+ * as `formatSketch` keeps only its link.
8647
+ */
8648
+ function sketchFieldTexts(blocks) {
8649
+ var texts = new Map();
8650
+ var _walk2 = function walk(node) {
8651
+ if (node.type === "text") {
8652
+ if (node.field && node.text && !texts.has(node.field)) {
8653
+ texts.set(node.field, node.text.replace(/\s+/g, " ").trim());
8654
+ }
8655
+ } else if (node.type === "row" || node.type === "col") {
8656
+ node.children.forEach(_walk2);
8657
+ }
8658
+ };
8659
+ blocks.forEach(_walk2);
8660
+ return texts;
8661
+ }
8662
+
8663
+ /** The pages a sketch is about, in the order it gives them. */
8664
+ function sketchRoutes(blocks) {
8665
+ return blocks.flatMap(function (block) {
8666
+ return block.type === "route" && block.route !== "" ? [block.route] : [];
8667
+ });
8668
+ }
8669
+
6540
8670
  /**
6541
8671
  * What a list names a sketch by: its first top-level heading, the biggest
6542
8672
  * one first. Null for a sketch with no heading, which a list then names by
@@ -7962,7 +10092,9 @@ function createSearchIndex() {
7962
10092
  tokenize: "forward"
7963
10093
  }),
7964
10094
  pathToLabel: new Map(),
7965
- docsByModule: new Map()
10095
+ docsByModule: new Map(),
10096
+ walkedByModule: new Map(),
10097
+ entriesByModule: new Map()
7966
10098
  };
7967
10099
  }
7968
10100
 
@@ -7976,77 +10108,230 @@ function createSearchIndex() {
7976
10108
  * `.jsonValues()` entries are indexed only once their content is loaded — an
7977
10109
  * un-loaded entry is an opaque marker that {@link traverseSchemaSource} skips, so
7978
10110
  * 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.
10111
+ * UI loads the entries on the first query and re-indexes as they arrive, rather
10112
+ * than pretending an empty result set is an answer -- and why a module whose
10113
+ * root is a record is re-indexed by entry ({@link indexRecordModule}): a pass
10114
+ * per batch walks the batch, not the record.
7981
10115
  */
7982
- function indexModule(searchIndex, moduleFilePath, source, schema) {
7983
- var index = searchIndex.index,
7984
- pathToLabel = searchIndex.pathToLabel,
7985
- docsByModule = searchIndex.docsByModule;
10116
+ function indexModule(searchIndex, moduleFilePath, source, schema,
10117
+ /**
10118
+ * Stop by this time (`performance.now()`), for a caller that must not hold
10119
+ * the thread: answers `false` when it stopped with entries of a record
10120
+ * module still to walk, and the same call again goes on from there. A
10121
+ * module that is not a record is walked whole, whatever the time.
10122
+ */
10123
+ until) {
10124
+ var walked = searchIndex.walkedByModule.get(moduleFilePath);
10125
+ if (walked !== undefined && walked.source === source && walked.schema === schemaKeyOf(schema)) {
10126
+ return true;
10127
+ }
10128
+ if (schema.type === "record" && isJsonRecord(source)) {
10129
+ var done = indexRecordModule(searchIndex, moduleFilePath, source, schema, until);
10130
+ if (done) {
10131
+ searchIndex.walkedByModule.set(moduleFilePath, {
10132
+ source: source,
10133
+ schema: schemaKeyOf(schema)
10134
+ });
10135
+ }
10136
+ return done;
10137
+ }
7986
10138
  removeModule(searchIndex, moduleFilePath);
7987
10139
  var docs = new Set();
7988
- docsByModule.set(moduleFilePath, docs);
10140
+ searchIndex.docsByModule.set(moduleFilePath, docs);
7989
10141
  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;
10142
+ traverseSchemaSource(source, schema, path, function (leaf) {
10143
+ var id = indexLeaf(searchIndex, leaf);
10144
+ if (id !== null) docs.add(id);
10145
+ });
10146
+ searchIndex.walkedByModule.set(moduleFilePath, {
10147
+ source: source,
10148
+ schema: schemaKeyOf(schema)
10149
+ });
10150
+ return true;
10151
+ }
10152
+
10153
+ /** How many entries are walked between two looks at the clock. */
10154
+ var ENTRIES_PER_CLOCK_CHECK = 32;
10155
+
10156
+ /**
10157
+ * A module whose root is a record, indexed entry by entry: an entry whose
10158
+ * value is the SAME OBJECT it was indexed from keeps its documents, and only
10159
+ * the others are walked.
10160
+ *
10161
+ * What makes loading a `.jsonValues()` record of 10,000 entries searchable
10162
+ * while it loads. The Studio asks again as each batch lands, and the record's
10163
+ * value is copy-on-write -- an entry nothing touched is the object it was --
10164
+ * so each pass indexes the batch that arrived, not the record. Walked whole,
10165
+ * that was the whole record per batch: quadratic, and a long task each time.
10166
+ *
10167
+ * Identity is only ever a shortcut: a value that is equal but a new object
10168
+ * (a structured clone across a worker, a module read again) is walked again,
10169
+ * never skipped. A different schema starts the module over.
10170
+ */
10171
+ function indexRecordModule(searchIndex, moduleFilePath, source, schema, until) {
10172
+ var index = searchIndex.index,
10173
+ pathToLabel = searchIndex.pathToLabel,
10174
+ docsByModule = searchIndex.docsByModule,
10175
+ entriesByModule = searchIndex.entriesByModule;
10176
+ var indexed = entriesByModule.get(moduleFilePath);
10177
+ var docs = docsByModule.get(moduleFilePath);
10178
+ if (indexed === undefined || docs === undefined || indexed.schema !== schema && schemaKeyOf(indexed.schema) !== schemaKeyOf(schema)) {
10179
+ removeModule(searchIndex, moduleFilePath);
10180
+ indexed = {
10181
+ schema: schema,
10182
+ entries: new Map()
10183
+ };
10184
+ docs = new Set();
10185
+ entriesByModule.set(moduleFilePath, indexed);
10186
+ docsByModule.set(moduleFilePath, docs);
10187
+ }
10188
+ var _indexed = indexed,
10189
+ entries = _indexed.entries;
10190
+ var _iterator = _createForOfIteratorHelper(entries),
10191
+ _step;
10192
+ try {
10193
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
10194
+ var _step$value = _slicedToArray(_step.value, 2),
10195
+ _key = _step$value[0],
10196
+ was = _step$value[1];
10197
+ if (Object.prototype.hasOwnProperty.call(source, _key) && source[_key] === was.source) {
10198
+ continue;
10199
+ }
10200
+ var _iterator2 = _createForOfIteratorHelper(was.docs),
10201
+ _step2;
10202
+ try {
10203
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
10204
+ var _id = _step2.value;
10205
+ index.remove(_id);
10206
+ pathToLabel["delete"](_id);
10207
+ docs["delete"](_id);
10208
+ }
10209
+ } catch (err) {
10210
+ _iterator2.e(err);
10211
+ } finally {
10212
+ _iterator2.f();
8036
10213
  }
10214
+ entries["delete"](_key);
8037
10215
  }
10216
+ } catch (err) {
10217
+ _iterator.e(err);
10218
+ } finally {
10219
+ _iterator.f();
10220
+ }
10221
+ var path = moduleFilePath + "?p=";
10222
+ var walked = 0;
10223
+ var _loop = function _loop() {
10224
+ if (entries.has(key)) return 0; // continue
10225
+ if (until !== undefined && ++walked % ENTRIES_PER_CLOCK_CHECK === 0 && performance.now() > until) {
10226
+ return {
10227
+ v: false
10228
+ };
10229
+ }
10230
+ var value = source[key];
10231
+ var entryDocs = [];
10232
+ traverseSchemaSource(value, schema.item, sourcePathOfChild(path, key), function (leaf) {
10233
+ var id = indexLeaf(searchIndex, leaf);
10234
+ if (id === null) return;
10235
+ entryDocs.push(id);
10236
+ docs.add(id);
10237
+ });
10238
+ entries.set(key, {
10239
+ source: value,
10240
+ docs: entryDocs
10241
+ });
10242
+ },
10243
+ _ret;
10244
+ for (var key in source) {
10245
+ _ret = _loop();
10246
+ if (_ret === 0) continue;
10247
+ if (_ret) return _ret.v;
10248
+ }
10249
+ return true;
10250
+ }
10251
+ function isJsonRecord(source) {
10252
+ return _typeof(source) === "object" && source !== null && !Array.isArray(source);
10253
+ }
10254
+
10255
+ /**
10256
+ * A schema by its content, kept per schema object. Its object is not enough:
10257
+ * the Studio receives a module's schema again (new code, a re-read of the
10258
+ * project) as a new object that says the same, and starting a record of
10259
+ * 10,000 entries over for that was seconds of removing and adding
10260
+ * documents in one go.
10261
+ */
10262
+ var schemaKeys = new WeakMap();
10263
+ function schemaKeyOf(schema) {
10264
+ var key = schemaKeys.get(schema);
10265
+ if (key === undefined) {
10266
+ key = JSON.stringify(schema);
10267
+ schemaKeys.set(schema, key);
10268
+ }
10269
+ return key;
10270
+ }
10271
+
10272
+ /** One leaf as a document, if it has anything to find. Its id, or `null`. */
10273
+ function indexLeaf(searchIndex, _ref) {
10274
+ var source = _ref.source,
10275
+ schema = _ref.schema,
10276
+ path = _ref.path;
10277
+ if (source === null) {
10278
+ return null;
10279
+ }
10280
+ var index = searchIndex.index,
10281
+ pathToLabel = searchIndex.pathToLabel;
10282
+ var searchText = "";
10283
+ var label = "";
8038
10284
 
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);
10285
+ // Handle primitives
10286
+ 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" ||
10287
+ // An enum's value is a string the editor chose; it is as searchable as
10288
+ // any other string leaf. The old string union was never indexed at all,
10289
+ // which is the gap this closes.
10290
+ schema.type === "enum" ||
10291
+ // A locale tag, for the same reason: it is what the editor picked and
10292
+ // what they see in the field, so searching "nb-NO" has to find the
10293
+ // Norwegian rows. `traverseSchemaSource` visits these, and visiting a
10294
+ // leaf only to index it as the empty string is worse than not visiting
10295
+ // it — it looks like content that does not exist.
10296
+ schema.type === "locale") {
10297
+ var _source$toString, _source$toString2;
10298
+ searchText = (_source$toString = source === null || source === void 0 ? void 0 : source.toString()) !== null && _source$toString !== void 0 ? _source$toString : "";
10299
+ label = (_source$toString2 = source === null || source === void 0 ? void 0 : source.toString()) !== null && _source$toString2 !== void 0 ? _source$toString2 : "";
10300
+ } else if (schema.type === "literal") {
10301
+ searchText = schema.value.toString();
10302
+ label = schema.value.toString();
10303
+ }
10304
+ // Handle richtext - flatten to get text content
10305
+ else if (schema.type === "richtext") {
10306
+ searchText = flattenRichText(source);
10307
+ // Use first 50 chars as label
10308
+ label = searchText.substring(0, 50) || "richtext";
10309
+ }
10310
+ // Handle file/image/video
10311
+ else if (schema.type === "file" || schema.type === "image" || schema.type === "video") {
10312
+ if (source !== null && _typeof(source) === "object" && "path" in source && typeof source.path === "string") {
10313
+ // The label is the bare filename - the folder is shown separately in
10314
+ // the UI - but both are searchable.
10315
+ var _getRefParts = getRefParts(source.path),
10316
+ filename = _getRefParts.filename,
10317
+ folder = _getRefParts.folder;
10318
+ var alt = "alt" in source ? source.alt : "";
10319
+ searchText = filename + " " + folder + " " + alt;
10320
+ label = filename;
8048
10321
  }
8049
- });
10322
+ }
10323
+
10324
+ // Add to index if we have search text
10325
+ if (!searchText) {
10326
+ return null;
10327
+ }
10328
+ var _Internal$splitModule = Internal.splitModuleFilePathAndModulePath(path),
10329
+ _Internal$splitModule2 = _slicedToArray(_Internal$splitModule, 2),
10330
+ modulePath = _Internal$splitModule2[1];
10331
+ var cleanPath = fastRemoveNonWordChars(modulePath) + " ";
10332
+ index.add(path, cleanPath + " " + path + " " + searchText);
10333
+ pathToLabel.set(path, label);
10334
+ return path;
8050
10335
  }
8051
10336
 
8052
10337
  /**
@@ -8058,21 +10343,24 @@ function indexModule(searchIndex, moduleFilePath, source, schema) {
8058
10343
  function removeModule(searchIndex, moduleFilePath) {
8059
10344
  var index = searchIndex.index,
8060
10345
  pathToLabel = searchIndex.pathToLabel,
8061
- docsByModule = searchIndex.docsByModule;
10346
+ docsByModule = searchIndex.docsByModule,
10347
+ entriesByModule = searchIndex.entriesByModule;
10348
+ entriesByModule["delete"](moduleFilePath);
10349
+ searchIndex.walkedByModule["delete"](moduleFilePath);
8062
10350
  var existing = docsByModule.get(moduleFilePath);
8063
10351
  if (existing === undefined) return;
8064
- var _iterator = _createForOfIteratorHelper(existing),
8065
- _step;
10352
+ var _iterator3 = _createForOfIteratorHelper(existing),
10353
+ _step3;
8066
10354
  try {
8067
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
8068
- var _id = _step.value;
8069
- index.remove(_id);
8070
- pathToLabel["delete"](_id);
10355
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
10356
+ var _id2 = _step3.value;
10357
+ index.remove(_id2);
10358
+ pathToLabel["delete"](_id2);
8071
10359
  }
8072
10360
  } catch (err) {
8073
- _iterator.e(err);
10361
+ _iterator3.e(err);
8074
10362
  } finally {
8075
- _iterator.f();
10363
+ _iterator3.f();
8076
10364
  }
8077
10365
  docsByModule["delete"](moduleFilePath);
8078
10366
  }
@@ -9547,4 +11835,4 @@ var messageOf = function messageOf(error) {
9547
11835
  return error instanceof Error ? error.message : String(error);
9548
11836
  };
9549
11837
 
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 };
11838
+ 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, SESSION_MEDIA_PREFIX, 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, sketchFieldTexts, sketchRoutes, sketchTitle, sourcePathOfChild, storeWork, themeCustomProperties, tiered, toolNames, traverseSchemaSource, urlOf, valClientHeaders, valModulesEntryFile, valModulesEntryPath, validateRoutePatterns, versionHashCount, wholeEntryKey, withExtension };