@valbuild/shared 0.150.18 → 0.151.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CHANGELOG.md +246 -0
  2. package/client/dist/valbuild-shared-client.cjs.dev.js +2 -1
  3. package/client/dist/valbuild-shared-client.cjs.prod.js +2 -1
  4. package/client/dist/valbuild-shared-client.esm.js +1 -1
  5. package/dist/declarations/src/internal/ApiRoutes.d.ts +253 -2
  6. package/dist/declarations/src/internal/content/applyChain.d.ts +124 -0
  7. package/dist/declarations/src/internal/content/buildRegistry.d.ts +36 -0
  8. package/dist/declarations/src/internal/content/jsonValuesOps.d.ts +110 -0
  9. package/dist/declarations/src/internal/content/moduleStore.d.ts +119 -0
  10. package/dist/declarations/src/internal/content/source.d.ts +71 -0
  11. package/dist/declarations/src/internal/content/version.d.ts +151 -0
  12. package/dist/declarations/src/internal/index.d.ts +8 -1
  13. package/dist/declarations/src/internal/publish/projectSource.d.ts +28 -0
  14. package/dist/declarations/src/internal/quickPreview.d.ts +17 -1
  15. package/dist/declarations/src/internal/search/searchIndex.d.ts +34 -3
  16. package/dist/declarations/src/internal/server/types.d.ts +9 -1
  17. package/dist/declarations/src/internal/valCanvasProtocol.d.ts +46 -0
  18. package/dist/declarations/src/internal/valClientHeader.d.ts +36 -0
  19. package/dist/declarations/src/publish/callJson.d.ts +15 -0
  20. package/dist/declarations/src/publish/jobClient.d.ts +7 -1
  21. package/dist/declarations/src/publish/publishClient.d.ts +2 -14
  22. package/dist/{getSitemapTree-7c848c94.esm.js → getSitemapTree-3c1b9e57.esm.js} +37 -4
  23. package/dist/{getSitemapTree-98642b57.cjs.dev.js → getSitemapTree-59c5ed5d.cjs.dev.js} +37 -3
  24. package/dist/{getSitemapTree-97713490.cjs.prod.js → getSitemapTree-6b9024d1.cjs.prod.js} +37 -3
  25. package/dist/{protocol-d0efb8cd.cjs.dev.js → projectSource-2a58ef15.cjs.dev.js} +221 -0
  26. package/dist/{protocol-55cfa3cf.esm.js → projectSource-7f0ee83c.esm.js} +216 -1
  27. package/dist/{protocol-b1055ef0.cjs.prod.js → projectSource-ed6aec39.cjs.prod.js} +221 -0
  28. package/internal/dist/valbuild-shared-internal.cjs.dev.js +2388 -189
  29. package/internal/dist/valbuild-shared-internal.cjs.prod.js +2388 -189
  30. package/internal/dist/valbuild-shared-internal.esm.js +2332 -172
  31. package/package.json +3 -3
  32. package/publish/dist/valbuild-shared-publish.cjs.dev.js +48 -155
  33. package/publish/dist/valbuild-shared-publish.cjs.prod.js +48 -155
  34. package/publish/dist/valbuild-shared-publish.esm.js +17 -123
  35. package/dist/declarations/src/internal/server/requestScopedMemo.d.ts +0 -49
@@ -5,11 +5,13 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  var createForOfIteratorHelper = require('../../dist/createForOfIteratorHelper-9ca2078d.cjs.prod.js');
6
6
  var core = require('@valbuild/core');
7
7
  var zod = require('zod');
8
- var protocol = require('../../dist/protocol-b1055ef0.cjs.prod.js');
8
+ var projectSource = require('../../dist/projectSource-ed6aec39.cjs.prod.js');
9
+ var wrapNativeSuper = require('../../dist/wrapNativeSuper-8673414f.cjs.prod.js');
9
10
  var zodValidationError = require('zod-validation-error');
10
- var getSitemapTree = require('../../dist/getSitemapTree-97713490.cjs.prod.js');
11
+ var fp = require('@valbuild/core/fp');
12
+ var patch = require('@valbuild/core/patch');
13
+ var getSitemapTree = require('../../dist/getSitemapTree-6b9024d1.cjs.prod.js');
11
14
  var FlexSearch = require('flexsearch');
12
- var wrapNativeSuper = require('../../dist/wrapNativeSuper-8673414f.cjs.prod.js');
13
15
  var utils = require('../../dist/utils-32bcaca2.cjs.prod.js');
14
16
 
15
17
  function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
@@ -204,7 +206,7 @@ var convertToNumber = function convertToNumber(value) {
204
206
  var parsedValue = parseInt(value, 10);
205
207
  return isNaN(parsedValue) ? 0 : parsedValue;
206
208
  };
207
- var textEncoder = new TextEncoder();
209
+ var textEncoder$1 = new TextEncoder();
208
210
  function convertImageNode(path, node, files, configDirectory, remoteOptions) {
209
211
  if (node.attrs && node.attrs.src.startsWith("data:")) {
210
212
  var binaryData = utils.base64DataUrlToUint8Array(node.attrs.src);
@@ -230,7 +232,7 @@ function convertImageNode(path, node, files, configDirectory, remoteOptions) {
230
232
  validationHash: core.Internal.remote.getValidationHash(remoteOptions.coreVersion, remoteOptions.schema, mimeType, {
231
233
  width: typeof node.attrs.width === "number" ? node.attrs.width : 0,
232
234
  height: typeof node.attrs.height === "number" ? node.attrs.height : 0
233
- }, remoteFileHash, textEncoder),
235
+ }, remoteFileHash, textEncoder$1),
234
236
  filePath: filePath.slice(1)
235
237
  })) : filePath;
236
238
  if (existingFilesEntry) {
@@ -748,62 +750,9 @@ var VAL_STATE_COOKIE = core.Internal.VAL_STATE_COOKIE;
748
750
  var VAL_ENABLE_COOKIE_NAME = core.Internal.VAL_ENABLE_COOKIE_NAME;
749
751
 
750
752
  /**
751
- * One value, computed once per REQUEST.
752
- *
753
- * `fetchVal` in draft mode reads the whole module tree out of `/sources/~`, and
754
- * a page that calls it three times used to ask three times — the answer being
755
- * byte-identical each time, because the query and the session are fixed for the
756
- * length of a request. This is what makes the second and third call free.
757
- *
758
- * ## Why the scope is the REQUEST
759
- *
760
- * The memoised response is a DRAFT -- unpublished work, read with the caller's
761
- * session cookie. Every caller allowed to read drafts gets the same draft (patch
762
- * groups, which made it per author, are gone), but whether a caller is allowed
763
- * at all is decided per session. A memo that outlived the request would hand
764
- * the draft to the next visitor without asking, and it would be invisible: the
765
- * page would simply render somebody's half-finished sentence. Sharing it across
766
- * requests is possible now, keyed on the chain's head, and would be its own
767
- * measured change.
768
- *
769
- * So this module holds NO state of its own. The caller supplies the box, and
770
- * the box has to come from something the framework already scopes to the
771
- * request — React's `cache()` in an RSC, a `WeakMap` keyed on the `Request` in
772
- * TanStack Start. `null` means "no request scope available", and that is not an
773
- * error: it falls back to computing every time, which is what the code did
774
- * before this existed. Ineffective is a safe failure here; leaking is not.
775
- *
776
- * The `key` is the second half of that guarantee. It carries whatever the
777
- * answer depends on — the session — so a box that somehow outlived its request,
778
- * or is shared by two sessions, MISSES rather than serving the wrong draft.
779
- *
780
- * ## The promise is cached, not the value
781
- *
782
- * React renders siblings concurrently, so three `fetchVal` calls in one page
783
- * are usually in flight at the same time rather than one after another.
784
- * Caching the resolved value would dedupe none of them; caching the promise
785
- * dedupes both orderings.
786
- */
787
-
788
- /**
789
- * Returns the memoised promise for `key`, computing it only on a miss.
790
- *
791
- * A rejection is memoised along with everything else, on purpose: one failed
792
- * read per request is the same answer for every caller in it, and retrying it
793
- * two more times only makes a broken draft slower to render.
753
+ * Nothing moved since the ETag the caller sent back: no body, as HTTP
754
+ * requires of a 304.
794
755
  */
795
- function memoizePerRequest(box, key, compute) {
796
- if (box === null) {
797
- return compute();
798
- }
799
- if (box.promise !== undefined && box.key === key) {
800
- return box.promise;
801
- }
802
- var promise = compute();
803
- box.key = key;
804
- box.promise = promise;
805
- return promise;
806
- }
807
756
 
808
757
  /**
809
758
  * JSON, recursively — the shape of a Source over the wire.
@@ -1054,7 +1003,7 @@ var readLegacyDirectoryAsDir = function readLegacyDirectoryAsDir(value) {
1054
1003
  }
1055
1004
  var _ref = value,
1056
1005
  directory = _ref.directory,
1057
- rest = protocol._objectWithoutProperties(_ref, _excluded);
1006
+ rest = projectSource._objectWithoutProperties(_ref, _excluded);
1058
1007
  return createForOfIteratorHelper._objectSpread2(createForOfIteratorHelper._objectSpread2({}, rest), {}, {
1059
1008
  dir: directory
1060
1009
  });
@@ -1309,6 +1258,228 @@ var ValDeployment = zod.z.object({
1309
1258
  commitMessage: zod.z.string().nullable().optional()
1310
1259
  });
1311
1260
 
1261
+ var _Internal$VERSION$cor;
1262
+
1263
+ /**
1264
+ * What a module's content IS: the schema it is read and patched with, the
1265
+ * base it starts from, and the chain of patches on top. Three parts because
1266
+ * each kind of change moves exactly one of them (see
1267
+ * `docs/plans/one-content-model.md`, "A version, per module, with the code in
1268
+ * it"):
1269
+ *
1270
+ * - a schema change, or a new engine (`@valbuild/*` upgraded), moves `schema`;
1271
+ * - a publish, a content version, a literal edited in code, moves `base`;
1272
+ * - an edit, a discard, a publish (which empties the chain) moves `chain`.
1273
+ *
1274
+ * Two answers with the same version are the same content, wherever they came
1275
+ * from. That is the whole point: comparing contents becomes comparing three
1276
+ * strings.
1277
+ *
1278
+ * Computed when content CHANGES -- a module evaluated, a base adopted, a patch
1279
+ * appended -- never when it is read (risk 3 in the plan): every function here
1280
+ * that hashes counts itself ({@link versionHashCount}), so a reader that
1281
+ * hashes is caught by a counted budget rather than by a profile.
1282
+ */
1283
+
1284
+ /**
1285
+ * What a project answer is relative to: which build served it, and how far
1286
+ * along the project's changes it is.
1287
+ */
1288
+
1289
+ /**
1290
+ * What the schema version of a module staged after the build serving a
1291
+ * proposal starts with: `staged:<inputs>`. Val's server has not got that
1292
+ * module's schema, so it names the staging instead; a Studio compares no
1293
+ * build's code against it.
1294
+ */
1295
+ var STAGED_SCHEMA_PREFIX = "staged:";
1296
+
1297
+ /** The chain of no patches. */
1298
+ var EMPTY_CHAIN = "";
1299
+
1300
+ /**
1301
+ * The engine: how patches are applied and content is read. Part of every
1302
+ * schema version, because content read by another engine is not the same
1303
+ * content even under the same schema.
1304
+ */
1305
+ var ENGINE = "@valbuild/core@".concat((_Internal$VERSION$cor = core.Internal.VERSION.core) !== null && _Internal$VERSION$cor !== void 0 ? _Internal$VERSION$cor : "unknown");
1306
+ var hashes = 0;
1307
+ var textEncoder;
1308
+ function sha256Hex(input) {
1309
+ hashes++;
1310
+ textEncoder !== null && textEncoder !== void 0 ? textEncoder : textEncoder = new TextEncoder();
1311
+ return core.Internal.getSHA256Hash(textEncoder.encode(input));
1312
+ }
1313
+
1314
+ /**
1315
+ * How many times a version has been hashed in this process: the counted
1316
+ * budget behind "versions are computed when content changes, never when it
1317
+ * is read" (`version:hash`). A test takes it before and after a read.
1318
+ */
1319
+ function versionHashCount() {
1320
+ return hashes;
1321
+ }
1322
+
1323
+ /** A chain one patch longer. */
1324
+ function extendChain(prev, id) {
1325
+ return sha256Hex("".concat(prev, "\n").concat(id));
1326
+ }
1327
+
1328
+ /** The chain of these patch ids, in order, on top of a base. */
1329
+ function chainOf(ids) {
1330
+ var chain = EMPTY_CHAIN;
1331
+ var _iterator = createForOfIteratorHelper._createForOfIteratorHelper(ids),
1332
+ _step;
1333
+ try {
1334
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
1335
+ var id = _step.value;
1336
+ chain = extendChain(chain, id);
1337
+ }
1338
+ } catch (err) {
1339
+ _iterator.e(err);
1340
+ } finally {
1341
+ _iterator.f();
1342
+ }
1343
+ return chain;
1344
+ }
1345
+
1346
+ /**
1347
+ * A module's schema version, from its serialized schema. The same function
1348
+ * on the server (from what `extractValModules` serialized) and in the Studio
1349
+ * (from what it holds in `SchemaStore`), so the two compare.
1350
+ */
1351
+ function schemaVersionOf(serializedSchema) {
1352
+ return sha256Hex("".concat(ENGINE, "\n").concat(JSON.stringify(serializedSchema)));
1353
+ }
1354
+
1355
+ /**
1356
+ * A module's base version, from its base source: what the module evaluates
1357
+ * to, or what a content version or a publish put in its place. `.jsonValues()`
1358
+ * entries are markers here; an entry's own version comes with entries stored
1359
+ * as modules of their own (phase 6).
1360
+ */
1361
+ function baseVersionOf(source) {
1362
+ var _JSON$stringify;
1363
+ return sha256Hex((_JSON$stringify = JSON.stringify(source)) !== null && _JSON$stringify !== void 0 ? _JSON$stringify : "undefined");
1364
+ }
1365
+
1366
+ /** The address of a module's content. Never changes meaning. */
1367
+ function moduleKey(path, v) {
1368
+ return "".concat(path, "@").concat(v.schema, ".").concat(v.base, ".").concat(v.chain);
1369
+ }
1370
+
1371
+ /** Whether two versions name the same content. */
1372
+ function sameModuleVersion(a, b) {
1373
+ return a.schema === b.schema && a.base === b.base && a.chain === b.chain;
1374
+ }
1375
+
1376
+ /**
1377
+ * The chains of every module, from the patches a draft applies (in chain
1378
+ * order), folded incrementally.
1379
+ *
1380
+ * A read asks for the chains every time; the fold is extended only by the
1381
+ * patches the chain has gained since the last call, and a call that finds the
1382
+ * chain as it was hashes nothing. A chain that changed anywhere but at its end
1383
+ * (a discard in the middle, a publish) is folded again from the start --
1384
+ * once, when that is first noticed.
1385
+ */
1386
+ var ChainFold = /*#__PURE__*/function () {
1387
+ function ChainFold() {
1388
+ wrapNativeSuper._classCallCheck(this, ChainFold);
1389
+ createForOfIteratorHelper._defineProperty(this, "ids", []);
1390
+ createForOfIteratorHelper._defineProperty(this, "paths", []);
1391
+ createForOfIteratorHelper._defineProperty(this, "chains", new Map());
1392
+ /**
1393
+ * Every module's chain after each of its patches, in order: what
1394
+ * {@link chainAt} answers for a module whose content stopped part way
1395
+ * along its chain (a patch that does not apply). Kept as the chains are
1396
+ * extended, so asking hashes nothing.
1397
+ */
1398
+ createForOfIteratorHelper._defineProperty(this, "prefixes", new Map());
1399
+ /**
1400
+ * Bumped whenever the chains move: a cheap identity for "the same chains as
1401
+ * before", in-process. With {@link instance} in front, two folds never name
1402
+ * one epoch: two processes (or isolates) holding the same chains answer
1403
+ * differently, which only costs a cache a miss, where two holding
1404
+ * DIFFERENT chains at the same count must never answer alike (an ETag
1405
+ * answered 304 for content the caller does not have).
1406
+ */
1407
+ createForOfIteratorHelper._defineProperty(this, "epochValue", 0);
1408
+ createForOfIteratorHelper._defineProperty(this, "instance", Math.random().toString(36).slice(2, 10));
1409
+ }
1410
+ return wrapNativeSuper._createClass(ChainFold, [{
1411
+ key: "chainsOf",
1412
+ value: /** Every module's chain, for these patches. Modules with none are absent. */
1413
+ function chainsOf(patches) {
1414
+ var known = this.ids.length;
1415
+ var extends_ = patches.length >= known;
1416
+ for (var i = 0; extends_ && i < known; i++) {
1417
+ var patch = patches[i];
1418
+ extends_ = patch !== undefined && patch.patchId === this.ids[i] && patch.path === this.paths[i];
1419
+ }
1420
+ if (extends_ && patches.length === known) {
1421
+ return this.chains;
1422
+ }
1423
+ var chains = extends_ ? new Map(this.chains) : new Map();
1424
+ var from = extends_ ? known : 0;
1425
+ if (!extends_) {
1426
+ this.ids = [];
1427
+ this.paths = [];
1428
+ this.prefixes = new Map();
1429
+ }
1430
+ for (var _i = from; _i < patches.length; _i++) {
1431
+ var _chains$get;
1432
+ var _patch = patches[_i];
1433
+ if (_patch === undefined) continue;
1434
+ var chain = extendChain((_chains$get = chains.get(_patch.path)) !== null && _chains$get !== void 0 ? _chains$get : EMPTY_CHAIN, _patch.patchId);
1435
+ chains.set(_patch.path, chain);
1436
+ var prefixes = this.prefixes.get(_patch.path);
1437
+ if (prefixes === undefined) this.prefixes.set(_patch.path, [chain]);else prefixes.push(chain);
1438
+ this.ids.push(_patch.patchId);
1439
+ this.paths.push(_patch.path);
1440
+ }
1441
+ this.chains = chains;
1442
+ this.epochValue++;
1443
+ return chains;
1444
+ }
1445
+
1446
+ /**
1447
+ * A module's chain after its first `count` patches, of those the last
1448
+ * {@link chainsOf} was given: the version of content that stopped at a
1449
+ * patch that does not apply, so the version names the chain the content
1450
+ * is AT. Hashes nothing.
1451
+ */
1452
+ }, {
1453
+ key: "chainAt",
1454
+ value: function chainAt(path, count) {
1455
+ var _this$prefixes$get, _ref, _prefixes;
1456
+ if (count <= 0) return EMPTY_CHAIN;
1457
+ var prefixes = (_this$prefixes$get = this.prefixes.get(path)) !== null && _this$prefixes$get !== void 0 ? _this$prefixes$get : [];
1458
+ 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;
1459
+ }
1460
+
1461
+ /** See {@link epochValue}. */
1462
+ }, {
1463
+ key: "epoch",
1464
+ value: function epoch() {
1465
+ return "".concat(this.instance, "-").concat(this.epochValue);
1466
+ }
1467
+ }]);
1468
+ }();
1469
+
1470
+ /** {@link ModuleVersion} on the wire. */
1471
+ var ModuleVersionSchema = zod.z.object({
1472
+ schema: zod.z.string(),
1473
+ base: zod.z.string(),
1474
+ chain: zod.z.string()
1475
+ });
1476
+
1477
+ /** {@link ProjectVersion} on the wire. */
1478
+ var ProjectVersionSchema = zod.z.object({
1479
+ build: zod.z.string(),
1480
+ seq: zod.z.number()
1481
+ });
1482
+
1312
1483
  /**
1313
1484
  * The wire shapes for reading history.
1314
1485
  *
@@ -1430,6 +1601,10 @@ var ModuleComparison = zod.z.object({
1430
1601
  verdict: RestoreVerdict
1431
1602
  });
1432
1603
 
1604
+ /** An `EntryVersion` on the wire. */
1605
+ var EntryVersionSchema = ModuleVersionSchema.extend({
1606
+ key: zod.z.string()
1607
+ });
1433
1608
  var ModuleFilePath = zod.z.string().refine(function (_path) {
1434
1609
  return true;
1435
1610
  } // TODO: validation
@@ -2052,11 +2227,112 @@ var Api = {
2052
2227
  })])
2053
2228
  }
2054
2229
  },
2230
+ /**
2231
+ * What each module IS, right now: its version (`schema`, `base`, `chain`),
2232
+ * and the project version that is relative to. What `/stat` answers, per
2233
+ * module; see `docs/plans/one-content-model.md`, "The Studio loads
2234
+ * everything from one place".
2235
+ *
2236
+ * It has its own ETag, so a client that sends it back (`If-None-Match`)
2237
+ * gets a 304 and no body while nothing has moved. Computed from versions
2238
+ * kept as content changes: answering it hashes nothing.
2239
+ *
2240
+ * "Since `seq` N" (`?since=N`, with the ETag of the manifest the client
2241
+ * holds, which was answered at `seq` N, as `If-None-Match`): the modules
2242
+ * whose version moved since that manifest, and no others, with `since: N`
2243
+ * on the answer to say it is a delta. The ETag is in the body too (`tag`),
2244
+ * for a client that reads answers through `ValClient`, which hands over
2245
+ * the body and not the headers. Answered whole -- no `since` -- when the server no
2246
+ * longer holds the manifest that ETag names (another process, another
2247
+ * build, too long ago), and by a server of the release before, which
2248
+ * ignores the parameter. Optional both ways.
2249
+ */
2250
+ "/manifest": {
2251
+ GET: {
2252
+ req: {
2253
+ cookies: {
2254
+ val_session: zod.z.string().optional()
2255
+ },
2256
+ headers: {
2257
+ "if-none-match": zod.z.string().optional()
2258
+ },
2259
+ query: {
2260
+ since: onlyOneStringQueryParam.optional()
2261
+ }
2262
+ },
2263
+ res: zod.z.union([unauthorizedResponse, zod.z.object({
2264
+ status: zod.z.literal(500),
2265
+ json: GenericError
2266
+ }), zod.z.object({
2267
+ status: zod.z.literal(200),
2268
+ json: zod.z.object({
2269
+ project: ProjectVersionSchema,
2270
+ modules: zod.z.record(ModuleFilePath, ModuleVersionSchema),
2271
+ /** This answer's ETag, as the header has it. */
2272
+ tag: zod.z.string().optional(),
2273
+ /**
2274
+ * Present on a delta only: the `seq` of the manifest it is
2275
+ * relative to, whose modules that are not named here did not
2276
+ * move. Absent: every module is named.
2277
+ */
2278
+ since: zod.z.number().optional(),
2279
+ /**
2280
+ * The build that answered is a previous build still answering
2281
+ * (http mode: the content service counted a newer one live).
2282
+ * Its versions are right for its own bases, which the site does
2283
+ * not serve any more: a Studio does not move to them, and asks
2284
+ * again. Optional: absent from a live build and a server of the
2285
+ * release before.
2286
+ */
2287
+ superseded: zod.z["boolean"]().optional(),
2288
+ /**
2289
+ * The answering build's `val.modules` registry, as an entry a
2290
+ * Studio can import (`valModulesEntryPath`), and the Val it
2291
+ * runs: where a Studio opened on another build takes this
2292
+ * build's code in without reloading the page. Only a build the
2293
+ * platform made and names (`publishBuild`) has one. Optional
2294
+ * both ways: absent from any other server (local development,
2295
+ * where HMR brings the code; a self-hosted deploy) and from a
2296
+ * server of the release before, and ignored by a Studio of the
2297
+ * release before.
2298
+ */
2299
+ registry: zod.z.object({
2300
+ entry: zod.z.string(),
2301
+ core: zod.z.string()
2302
+ }).optional()
2303
+ })
2304
+ }),
2305
+ /*
2306
+ * Nothing moved since the ETag the client sent. No body on the wire;
2307
+ * the client reads it as `json: null`.
2308
+ */
2309
+ zod.z.object({
2310
+ status: zod.z.literal(304),
2311
+ json: zod.z["null"]().optional()
2312
+ })])
2313
+ }
2314
+ },
2055
2315
  "/stat": {
2056
2316
  POST: {
2057
2317
  req: {
2058
2318
  body: zod.z.object({
2319
+ /**
2320
+ * @deprecated Echoed by the Studio, and compared by the fs and
2321
+ * memory long poll to see that another build answers. This
2322
+ * release's Studio reads each module's version from
2323
+ * `GET /manifest`; goes once the long poll compares the echoed
2324
+ * version instead (see "Release-delayed deletions" in
2325
+ * `docs/plans/one-content-model.md`).
2326
+ */
2059
2327
  sourcesSha: zod.z.string(),
2328
+ /**
2329
+ * @deprecated Echoed by the Studio, and compared by the fs and
2330
+ * memory long poll to see that another build answers. This
2331
+ * release's Studio reads each module's version from
2332
+ * `GET /manifest`; goes once the long poll compares the echoed
2333
+ * version instead (see "Release-delayed deletions" in
2334
+ * `docs/plans/one-content-model.md`).
2335
+ */
2060
2336
  schemaSha: zod.z.string(),
2061
2337
  baseSha: zod.z.string(),
2062
2338
  patches: zod.z.array(zod.z.string()).optional(),
@@ -2086,7 +2362,21 @@ var Api = {
2086
2362
  json: zod.z.union([zod.z.object({
2087
2363
  type: zod.z.union([zod.z.literal("request-again"), zod.z.literal("no-change"), zod.z.literal("did-change")]),
2088
2364
  baseSha: zod.z.string(),
2365
+ /**
2366
+ * @deprecated Read by a Studio of the release before
2367
+ * (`BaseAlignment`, `SchemaFreshnessWatch`); this release's
2368
+ * only echoes it, and reads each module's version from
2369
+ * `GET /manifest`. Goes in two steps (see "Release-delayed
2370
+ * deletions" in `docs/plans/one-content-model.md`).
2371
+ */
2089
2372
  schemaSha: zod.z.string(),
2373
+ /**
2374
+ * @deprecated Read by a Studio of the release before
2375
+ * (`BaseAlignment`, `SchemaFreshnessWatch`); this release's
2376
+ * only echoes it, and reads each module's version from
2377
+ * `GET /manifest`. Goes in two steps (see "Release-delayed
2378
+ * deletions" in `docs/plans/one-content-model.md`).
2379
+ */
2090
2380
  sourcesSha: zod.z.string(),
2091
2381
  patches: zod.z.array(PatchId),
2092
2382
  /**
@@ -2184,13 +2474,34 @@ var Api = {
2184
2474
  * the git mirror's or the one content reports for the project.
2185
2475
  * Absent where there is no history to show.
2186
2476
  */
2187
- historyBranch: zod.z.string().optional()
2477
+ historyBranch: zod.z.string().optional(),
2478
+ /**
2479
+ * What this answer is relative to: the build that served it
2480
+ * and how far along the project's changes it is (`seq`). Of two
2481
+ * answers, the higher `seq` is the newer. Optional: a server
2482
+ * that predates versions sends none.
2483
+ */
2484
+ version: ProjectVersionSchema.optional()
2188
2485
  }), zod.z.object({
2189
2486
  type: zod.z.literal("use-websocket"),
2190
2487
  url: zod.z.string(),
2191
2488
  nonce: zod.z.string(),
2192
2489
  baseSha: zod.z.string(),
2490
+ /**
2491
+ * @deprecated Read by a Studio of the release before
2492
+ * (`BaseAlignment`, `SchemaFreshnessWatch`); this release's
2493
+ * only echoes it, and reads each module's version from
2494
+ * `GET /manifest`. Goes in two steps (see "Release-delayed
2495
+ * deletions" in `docs/plans/one-content-model.md`).
2496
+ */
2193
2497
  schemaSha: zod.z.string(),
2498
+ /**
2499
+ * @deprecated Read by a Studio of the release before
2500
+ * (`BaseAlignment`, `SchemaFreshnessWatch`); this release's
2501
+ * only echoes it, and reads each module's version from
2502
+ * `GET /manifest`. Goes in two steps (see "Release-delayed
2503
+ * deletions" in `docs/plans/one-content-model.md`).
2504
+ */
2194
2505
  sourcesSha: zod.z.string(),
2195
2506
  /**
2196
2507
  * The commit this deployment was built from.
@@ -2312,7 +2623,14 @@ var Api = {
2312
2623
  * the git mirror's or the one content reports for the project.
2313
2624
  * Absent where there is no history to show.
2314
2625
  */
2315
- historyBranch: zod.z.string().optional()
2626
+ historyBranch: zod.z.string().optional(),
2627
+ /**
2628
+ * What this answer is relative to: the build that served it
2629
+ * and how far along the project's changes it is (`seq`). Of two
2630
+ * answers, the higher `seq` is the newer. Optional: a server
2631
+ * that predates versions sends none.
2632
+ */
2633
+ version: ProjectVersionSchema.optional()
2316
2634
  })])
2317
2635
  })])
2318
2636
  }
@@ -2354,6 +2672,14 @@ var Api = {
2354
2672
  path: zod.z.string().optional(),
2355
2673
  cookies: {
2356
2674
  val_session: zod.z.string().optional()
2675
+ },
2676
+ /*
2677
+ * Carried to content, which answers 304 when it names the answer
2678
+ * (`/project-source` does: valbuild/home sends its `ETag`). The
2679
+ * `ETag` comes back as a header, and a 304 with no body.
2680
+ */
2681
+ headers: {
2682
+ "if-none-match": zod.z.string().optional()
2357
2683
  }
2358
2684
  },
2359
2685
  /*
@@ -2365,10 +2691,18 @@ var Api = {
2365
2691
  * `{ message }` rather than throwing, because a publish failing is
2366
2692
  * something the editor has to be told about in words.
2367
2693
  */
2368
- res: zod.z.object({
2694
+ res: zod.z.union([zod.z.object({
2369
2695
  status: zod.z.number(),
2370
2696
  json: zod.z.unknown()
2371
- })
2697
+ }),
2698
+ /*
2699
+ * Content said the caller holds the answer already (`If-None-Match`).
2700
+ * No body on the wire: a 304 cannot carry one.
2701
+ */
2702
+ zod.z.object({
2703
+ status: zod.z.literal(304),
2704
+ json: zod.z["null"]().optional()
2705
+ })])
2372
2706
  },
2373
2707
  POST: {
2374
2708
  req: {
@@ -2804,7 +3138,9 @@ var Api = {
2804
3138
  })),
2805
3139
  baseSha: zod.z.string(),
2806
3140
  error: GenericError.optional(),
2807
- errors: zod.z.record(PatchId, GenericError).optional()
3141
+ errors: zod.z.record(PatchId, GenericError).optional(),
3142
+ /** The project version these patches were read at. See `/stat`. */
3143
+ version: ProjectVersionSchema.optional()
2808
3144
  })
2809
3145
  })])
2810
3146
  }
@@ -2837,6 +3173,15 @@ var Api = {
2837
3173
  })])
2838
3174
  }
2839
3175
  },
3176
+ /**
3177
+ * @deprecated The whole tree. Nothing of this release asks it: the Studio
3178
+ * reads modules by version (`GET /manifest`, `GET /modules/~`), the draft
3179
+ * readers and MCP read the head in-process. Kept, with `exclude_patches`
3180
+ * (`BaseAlignment`'s fetch) and the ignored `validate_sources` and
3181
+ * `validate_binary_files`, for a Studio of the release before; goes in the
3182
+ * release after the one that ships this (see "Release-delayed deletions"
3183
+ * in `docs/plans/one-content-model.md`).
3184
+ */
2840
3185
  "/sources/~": {
2841
3186
  PUT: {
2842
3187
  req: {
@@ -2888,12 +3233,100 @@ var Api = {
2888
3233
  skipped: zod.z.array(PatchId).optional(),
2889
3234
  errors: zod.z.record(PatchId, GenericError).optional()
2890
3235
  }).optional(),
2891
- validationErrors: zod.z.record(zod.z.string(), zod.z.array(ValidationError)).optional()
2892
- }))
3236
+ validationErrors: zod.z.record(zod.z.string(), zod.z.array(ValidationError)).optional(),
3237
+ /**
3238
+ * The version of `source` as answered: with the chain the
3239
+ * server applied, or the empty chain when it applied none
3240
+ * (`apply_patches=false`). Optional: an older server sends
3241
+ * none.
3242
+ */
3243
+ version: ModuleVersionSchema.optional()
3244
+ })),
3245
+ /** The project version this tree was read at. See `/stat`. */
3246
+ project: ProjectVersionSchema.optional()
2893
3247
  })
2894
3248
  })])
2895
3249
  }
2896
3250
  },
3251
+ /**
3252
+ * One module by version: phase 6 of `docs/plans/one-content-model.md`
3253
+ * ("The Studio loads everything from one place"). `GET /modules/~<path>`,
3254
+ * with `v` the version wanted as its `moduleKey` (the newest when absent;
3255
+ * a base is a version with the empty chain) and `have` the version the
3256
+ * caller holds: when the caller's chain is a start of the answer's under
3257
+ * the same schema and base, the answer is the patches from it (`delta`),
3258
+ * otherwise the whole module. An answer for an asked `v` is immutable.
3259
+ */
3260
+ "/modules/~": {
3261
+ GET: {
3262
+ req: {
3263
+ path: zod.z.string(),
3264
+ query: {
3265
+ v: onlyOneStringQueryParam.optional(),
3266
+ have: onlyOneStringQueryParam.optional()
3267
+ },
3268
+ cookies: {
3269
+ val_session: zod.z.string().optional()
3270
+ }
3271
+ },
3272
+ res: zod.z.union([unauthorizedResponse, zod.z.object({
3273
+ status: zod.z.literal(404),
3274
+ json: zod.z.object({
3275
+ message: zod.z.string(),
3276
+ /** The version held now, when the one asked for is not. */
3277
+ current: ModuleVersionSchema.optional()
3278
+ })
3279
+ }), zod.z.object({
3280
+ status: zod.z.literal(500),
3281
+ json: GenericError
3282
+ }), zod.z.object({
3283
+ status: zod.z.literal(200),
3284
+ headers: zod.z.object({
3285
+ "Cache-Control": zod.z.string()
3286
+ }).optional(),
3287
+ json: zod.z.union([zod.z.object({
3288
+ kind: zod.z.literal("module"),
3289
+ version: ModuleVersionSchema,
3290
+ source: zod.z.any(),
3291
+ /**
3292
+ * The `.jsonValues()` entries the module's chain touched, read,
3293
+ * each with its own version. Every other entry is the base's,
3294
+ * behind its marker in `source`.
3295
+ */
3296
+ entries: zod.z.record(zod.z.string(), zod.z.union([zod.z.object({
3297
+ content: zod.z.any(),
3298
+ version: EntryVersionSchema
3299
+ }), zod.z.object({
3300
+ deleted: zod.z.literal(true),
3301
+ version: EntryVersionSchema
3302
+ }), zod.z.object({
3303
+ error: zod.z.string(),
3304
+ /** The patch that made it unreadable, when known. */
3305
+ patchId: PatchId.optional(),
3306
+ version: EntryVersionSchema
3307
+ })])),
3308
+ /**
3309
+ * The patch ids its chain is made of, in chain order: what a
3310
+ * client holding this knows the server already has. Optional:
3311
+ * a server of the release before sends none.
3312
+ */
3313
+ patches: zod.z.array(PatchId).optional(),
3314
+ failed: zod.z.object({
3315
+ patchId: PatchId,
3316
+ message: zod.z.string()
3317
+ }).optional()
3318
+ }), zod.z.object({
3319
+ kind: zod.z.literal("delta"),
3320
+ from: ModuleVersionSchema,
3321
+ to: ModuleVersionSchema,
3322
+ patches: zod.z.array(zod.z.object({
3323
+ patchId: PatchId,
3324
+ patch: Patch
3325
+ }))
3326
+ })])
3327
+ })])
3328
+ }
3329
+ },
2897
3330
  /**
2898
3331
  * The Studio's full check, one module per request: its sources against its
2899
3332
  * schema and its local files against their bytes, on the content the caller
@@ -3614,7 +4047,14 @@ var Api = {
3614
4047
  path: ModuleFilePath,
3615
4048
  key: zod.z.string(),
3616
4049
  // The entry's JSON content (or null if the entry has no value).
3617
- content: zod.z.any()
4050
+ content: zod.z.any(),
4051
+ /**
4052
+ * The version of the record module the entry was read from, with
4053
+ * the chain that was applied (the empty chain without
4054
+ * `apply_patches`). An entry's own version comes with entries
4055
+ * stored as modules of their own.
4056
+ */
4057
+ version: ModuleVersionSchema.optional()
3618
4058
  })
3619
4059
  }),
3620
4060
  // Shapes (2) and (3): `keys` or `offset`+`limit`.
@@ -3637,7 +4077,9 @@ var Api = {
3637
4077
  // All-mode only: the resolved window and the record's total key count.
3638
4078
  offset: zod.z.number().optional(),
3639
4079
  limit: zod.z.number().optional(),
3640
- total: zod.z.number().optional()
4080
+ total: zod.z.number().optional(),
4081
+ /** As for a single `key`. */
4082
+ version: ModuleVersionSchema.optional()
3641
4083
  })
3642
4084
  }), zod.z.object({
3643
4085
  status: zod.z.literal(400),
@@ -4100,8 +4542,8 @@ var createValClient = function createValClient(host, config, options) {
4100
4542
  body: anyReq.body !== undefined ? JSON.stringify(anyReq.body) : undefined
4101
4543
  }).then(/*#__PURE__*/function () {
4102
4544
  var _ref2 = createForOfIteratorHelper._asyncToGenerator(/*#__PURE__*/createForOfIteratorHelper._regenerator().m(function _callee(res) {
4103
- var _apiEndpoint$res;
4104
- var contentTypeHeaders, json, valClientResult, message, responseResult;
4545
+ var _apiEndpoint$res2;
4546
+ var contentTypeHeaders, _apiEndpoint$res, notModified, json, valClientResult, message, responseResult;
4105
4547
  return createForOfIteratorHelper._regenerator().w(function (_context) {
4106
4548
  while (1) switch (_context.n) {
4107
4549
  case 0:
@@ -4119,10 +4561,38 @@ var createValClient = function createValClient(host, config, options) {
4119
4561
  }
4120
4562
  });
4121
4563
  case 1:
4122
- if (contentTypeHeaders !== null && contentTypeHeaders !== void 0 && contentTypeHeaders.includes("application/json")) {
4564
+ if (!(res.status === 304)) {
4565
+ _context.n = 3;
4566
+ break;
4567
+ }
4568
+ notModified = {
4569
+ status: 304,
4570
+ json: null
4571
+ };
4572
+ if (!(((_apiEndpoint$res = apiEndpoint.res) === null || _apiEndpoint$res === void 0 ? void 0 : _apiEndpoint$res.safeParse(notModified).success) === false)) {
4123
4573
  _context.n = 2;
4124
4574
  break;
4125
4575
  }
4576
+ return _context.a(2, {
4577
+ status: null,
4578
+ json: {
4579
+ type: "client_side_validation_error",
4580
+ message: "Invalid response (304 from a route that does not answer one). This could be a result of mismatched Val versions.",
4581
+ details: {
4582
+ validationError: "Unexpected 304",
4583
+ data: {
4584
+ status: res.status
4585
+ }
4586
+ }
4587
+ }
4588
+ });
4589
+ case 2:
4590
+ return _context.a(2, notModified);
4591
+ case 3:
4592
+ if (contentTypeHeaders !== null && contentTypeHeaders !== void 0 && contentTypeHeaders.includes("application/json")) {
4593
+ _context.n = 4;
4594
+ break;
4595
+ }
4126
4596
  return _context.a(2, {
4127
4597
  status: null,
4128
4598
  json: {
@@ -4137,17 +4607,17 @@ var createValClient = function createValClient(host, config, options) {
4137
4607
  }
4138
4608
  }
4139
4609
  });
4140
- case 2:
4141
- _context.n = 3;
4610
+ case 4:
4611
+ _context.n = 5;
4142
4612
  return res.json();
4143
- case 3:
4613
+ case 5:
4144
4614
  json = _context.v;
4145
4615
  valClientResult = {
4146
4616
  status: res.status,
4147
4617
  json: json
4148
4618
  };
4149
4619
  if (!(res.status === 500)) {
4150
- _context.n = 4;
4620
+ _context.n = 6;
4151
4621
  break;
4152
4622
  }
4153
4623
  core.Internal.log.error("Server responded with an error", json);
@@ -4159,10 +4629,10 @@ var createValClient = function createValClient(host, config, options) {
4159
4629
  message: message
4160
4630
  }
4161
4631
  });
4162
- case 4:
4163
- responseResult = (_apiEndpoint$res = apiEndpoint.res) === null || _apiEndpoint$res === void 0 ? void 0 : _apiEndpoint$res.safeParse(valClientResult);
4632
+ case 6:
4633
+ responseResult = (_apiEndpoint$res2 = apiEndpoint.res) === null || _apiEndpoint$res2 === void 0 ? void 0 : _apiEndpoint$res2.safeParse(valClientResult);
4164
4634
  if (!(responseResult && !responseResult.success)) {
4165
- _context.n = 5;
4635
+ _context.n = 7;
4166
4636
  break;
4167
4637
  }
4168
4638
  return _context.a(2, {
@@ -4176,7 +4646,7 @@ var createValClient = function createValClient(host, config, options) {
4176
4646
  }
4177
4647
  }
4178
4648
  });
4179
- case 5:
4649
+ case 7:
4180
4650
  return _context.a(2, {
4181
4651
  status: res.status,
4182
4652
  json: json
@@ -4320,6 +4790,44 @@ var urlOf = function urlOf() {
4320
4790
  return "".concat(route);
4321
4791
  };
4322
4792
 
4793
+ /**
4794
+ * Which Val package, at which version, sent a request to Val Build.
4795
+ *
4796
+ * Sent on every request a Val package makes to Val's own services from a
4797
+ * server or a CLI -- the content host, `/api/login`, the analytics ingest --
4798
+ * so Val Build can tell which versions are still out there and which old
4799
+ * endpoints they still call. Without it an endpoint can never be retired with
4800
+ * any confidence: a request from a site nobody has upgraded in a year looks
4801
+ * exactly like one from yesterday's release.
4802
+ *
4803
+ * The value is a product token, `@valbuild/server@0.152.0`: the package whose
4804
+ * code built the request, because that is what decides which endpoints are
4805
+ * called. Not the framework binding or the app: those do not choose URLs.
4806
+ *
4807
+ * NOT sent from the browser. A custom header on a cross-origin request makes
4808
+ * the browser send a CORS preflight, and a host that does not list the header
4809
+ * in `Access-Control-Allow-Headers` refuses the request outright -- so adding
4810
+ * it there is a change to every such host first.
4811
+ *
4812
+ * It identifies the code, not the caller: anyone can send any value, so it is
4813
+ * for counting, never for deciding what a request may do.
4814
+ */
4815
+ var VAL_CLIENT_HEADER = "x-val-client";
4816
+
4817
+ /** Packages that send {@link VAL_CLIENT_HEADER}. */
4818
+
4819
+ /**
4820
+ * `{ "x-val-client": "@valbuild/server@0.152.0" }`, to spread into a
4821
+ * request's headers.
4822
+ *
4823
+ * A version that could not be read is sent as `unknown` rather than leaving
4824
+ * the header out: "a server too broken to know its version" is still a thing
4825
+ * worth counting, and it is not the same as "a version older than the header".
4826
+ */
4827
+ function valClientHeaders(pkg, version) {
4828
+ return createForOfIteratorHelper._defineProperty({}, VAL_CLIENT_HEADER, "".concat(pkg, "@").concat(version || "unknown"));
4829
+ }
4830
+
4323
4831
  /**
4324
4832
  * Can this value, taken from one schema, be written into another?
4325
4833
  *
@@ -4753,23 +5261,1517 @@ function chainHeadOf(reportedHead, listed) {
4753
5261
  return (_listed = listed[listed.length - 1]) !== null && _listed !== void 0 ? _listed : null;
4754
5262
  }
4755
5263
 
4756
- var SharedValConfig = zod.z.object({
4757
- contentHostUrl: zod.z.string().optional(),
4758
- appHostUrl: zod.z.string().optional(),
4759
- project: zod.z.string().optional(),
4760
- root: zod.z.string().optional(),
4761
- gitCommit: zod.z.string().optional(),
4762
- gitBranch: zod.z.string().optional(),
4763
- defaultTheme: zod.z["enum"](["dark", "light"]).optional(),
4764
- ai: zod.z.object({
4765
- commitMessages: zod.z.object({
4766
- disabled: zod.z["boolean"]().optional()
4767
- }).optional()
4768
- }).optional()
4769
- });
4770
-
4771
5264
  /**
4772
- * Config that is shared between the client and server.
5265
+ * Where a build's `val.modules` registry can be imported from: phase 9 of
5266
+ * `docs/plans/one-content-model.md`, "Reloading the schema while the Studio
5267
+ * runs".
5268
+ *
5269
+ * A build made by `@valbuild/tanstack-build` (every build the platform
5270
+ * serves) emits its `val.modules` as an ES module of its own beside its
5271
+ * client chunks, named after the build: `/_app/val-modules-<build>.js`,
5272
+ * whose default export is the registry. The server running that build knows
5273
+ * its name (`VAL_BUILD`, Val's `publishBuild`) and names the entry on
5274
+ * `GET /manifest` (`registry`), so a Studio opened on an older build imports
5275
+ * the new one's code and reloads it in place (`HostStore.reloadCode`)
5276
+ * instead of being told to reload the page.
5277
+ *
5278
+ * The convention is the builder's and this file's together: the builder
5279
+ * writes the file (`valModulesEntryFile` in `@valbuild/tanstack-build`, which
5280
+ * does not depend on this package), and each side pins the same literal,
5281
+ * `val-modules-<build>.js` (`buildRegistry.test.ts` here, `valModules.test.ts`
5282
+ * there).
5283
+ */
5284
+
5285
+ /** The file a build's registry is emitted as, among its client chunks. */
5286
+ function valModulesEntryFile(build) {
5287
+ return "val-modules-".concat(build, ".js");
5288
+ }
5289
+
5290
+ /** Where a build served by the platform serves its registry. */
5291
+ function valModulesEntryPath(build) {
5292
+ return "/_app/".concat(valModulesEntryFile(build));
5293
+ }
5294
+
5295
+ /**
5296
+ * What `GET /manifest` says about the code of the build that answered:
5297
+ * where its registry is, and which Val it runs. A Studio running another
5298
+ * Val cannot take the build's code in (its own stores would run against a
5299
+ * new core), so it reloads itself instead (`other-val-version`).
5300
+ */
5301
+
5302
+ /*
5303
+ * How a patch op relates to a `.jsonValues()` record: which ops reach into an
5304
+ * entry, what a write of the whole record says about each entry, and how an
5305
+ * op inside an entry is rebased onto the entry's own file.
5306
+ *
5307
+ * Here, in shared, because the engine (`applyChain.ts`) is built on them and
5308
+ * runs wherever content is patched; the commit flow (`ValOps.prepare`), the
5309
+ * CLI's fixes and the language server use the same functions, through
5310
+ * `@valbuild/server`, which re-exports them. One rule, one implementation:
5311
+ * a draft that shows one thing while publishing writes another is the failure
5312
+ * nobody sees until it is published.
5313
+ */
5314
+
5315
+ /**
5316
+ * Classification of a single patch op against a module's serialized schema,
5317
+ * used by the commit flow to route ops for `.jsonValues()` records:
5318
+ *
5319
+ * - `normal`: the op does not descend into a `.jsonValues()` entry; apply it to
5320
+ * the `.val.ts` as usual.
5321
+ * - `entry`: the op targets a `.jsonValues()` entry. `recordPath` is the path to
5322
+ * the record within the module source (empty for a root record/router),
5323
+ * `entryKey` is the entry key, and `subPath` is the remaining path inside the
5324
+ * entry (empty when the op targets the entry value itself, e.g. add/remove of
5325
+ * the whole entry).
5326
+ */
5327
+
5328
+ /**
5329
+ * Walks the serialized schema following the op path. When a `.jsonValues()`
5330
+ * record is encountered, the next path segment is the entry key and everything
5331
+ * after it lives inside the entry's `*.val.json` (so it does not touch the
5332
+ * `.val.ts`). Returns `{ kind: "normal" }` when the op never enters a
5333
+ * `.jsonValues()` record.
5334
+ */
5335
+ function classifyJsonValuesOp(schema, opPath) {
5336
+ var current = schema;
5337
+ var recordPath = [];
5338
+ for (var i = 0; i < opPath.length; i++) {
5339
+ if (!current) {
5340
+ return {
5341
+ kind: "normal"
5342
+ };
5343
+ }
5344
+ if (current.type === "record" && current.jsonValues) {
5345
+ return {
5346
+ kind: "entry",
5347
+ recordPath: recordPath.slice(),
5348
+ entryKey: opPath[i],
5349
+ subPath: opPath.slice(i + 1)
5350
+ };
5351
+ }
5352
+ var seg = opPath[i];
5353
+ current = descend(current, seg);
5354
+ recordPath.push(seg);
5355
+ }
5356
+ return {
5357
+ kind: "normal"
5358
+ };
5359
+ }
5360
+ function descend(schema, key) {
5361
+ switch (schema.type) {
5362
+ case "object":
5363
+ return schema.items[key];
5364
+ case "record":
5365
+ return schema.item;
5366
+ case "array":
5367
+ return schema.item;
5368
+ default:
5369
+ // Unions / primitives / leaf schemas: we cannot (or need not) descend
5370
+ // further to find a jsonValues record. Anything below is a normal
5371
+ // `.val.ts` edit.
5372
+ return undefined;
5373
+ }
5374
+ }
5375
+
5376
+ /**
5377
+ * Is this op a write of the WHOLE record at the root of a `.jsonValues()`
5378
+ * module?
5379
+ *
5380
+ * The one op {@link classifyJsonValuesOp} cannot classify: it finds the entry
5381
+ * key by walking the op path, and the root path has no segments to walk, so a
5382
+ * root write reads as `normal` and gets applied to the `.val.ts` - writing
5383
+ * markers over the `c.json(() => import(...))` calls that make the entries load
5384
+ * at all. {@link expandJsonValuesRootOp} turns it into ops that DO name a key,
5385
+ * which everything downstream already handles.
5386
+ *
5387
+ * `add` counts as well as `replace`: at the root both mean "the document is now
5388
+ * this" (see `JSONOps`), so both have to be expanded or the unexpanded one is
5389
+ * the same bug again.
5390
+ *
5391
+ * The value has to BE a record, and that is part of the question rather than a
5392
+ * check inside the expansion: a `.jsonValues()` record can be nullable, and
5393
+ * `null` at the root means the module no longer has a record at all - there are
5394
+ * no entries to write, and it is an ordinary `.val.ts` write, exactly as it was
5395
+ * before any of this existed. Same for any other non-record value: not a
5396
+ * whole-record write, so not this conversion's to route.
5397
+ */
5398
+ function isJsonValuesRootOp(schema, op) {
5399
+ return (op.op === "replace" || op.op === "add") && op.path.length === 0 && isJsonValuesRecord(op.value) && schema.type === "record" && schema.jsonValues === true;
5400
+ }
5401
+ function isJsonValuesRecord(value) {
5402
+ return createForOfIteratorHelper._typeof(value) === "object" && value !== null && !Array.isArray(value);
5403
+ }
5404
+
5405
+ /**
5406
+ * One entry as it stands right now, for {@link expandJsonValuesRootOp} to
5407
+ * expand against.
5408
+ *
5409
+ * The key set decides add-vs-replace-vs-remove. The content, where the caller
5410
+ * has it, decides whether an op is emitted at all: an entry whose content is
5411
+ * already what the root write says is left alone rather than rewritten, so
5412
+ * putting a module back does not touch every `*.val.json` it did not change.
5413
+ * `undefined` means "this entry exists, content unknown" - which is what the
5414
+ * Studio's draft source has, because the source holds markers - and yields a
5415
+ * `replace`, the safe answer.
5416
+ */
5417
+
5418
+ /**
5419
+ * Fans a whole-record write at a `.jsonValues()` module's root out into ops
5420
+ * that each name an entry key.
5421
+ *
5422
+ * This is THE conversion, and it has exactly one implementation on purpose: the
5423
+ * commit flow (`ValOps.prepare`) and the engine that builds draft content
5424
+ * (`applyPatchRecord`) both expand through here, so a draft
5425
+ * cannot show something other than what publishing writes. A second
5426
+ * implementation of the same rule would differ silently, which is the whole
5427
+ * failure mode this exists to prevent.
5428
+ *
5429
+ * The value must be the entries' CONTENT. A module's Source is markers, not
5430
+ * content, so a Source handed over here is refused rather than written: that is
5431
+ * the shape of the original bug (a revert replaying the archived Source), and
5432
+ * it is not recoverable afterwards - the markers replace the entries' content
5433
+ * on disk.
5434
+ */
5435
+ function expandJsonValuesRootOp(op, currentEntries) {
5436
+ var valueRes = jsonValuesRootValue(op);
5437
+ if (fp.result.isErr(valueRes)) {
5438
+ return valueRes;
5439
+ }
5440
+ var value = valueRes.value;
5441
+ var expanded = [];
5442
+ for (var _i = 0, _Object$keys = Object.keys(value); _i < _Object$keys.length; _i++) {
5443
+ var entryKey = _Object$keys[_i];
5444
+ var write = writeForEntry(value, entryKey, currentEntries);
5445
+ if (write !== null) {
5446
+ expanded.push(write);
5447
+ }
5448
+ }
5449
+ var _iterator = createForOfIteratorHelper._createForOfIteratorHelper(currentEntries.keys()),
5450
+ _step;
5451
+ try {
5452
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
5453
+ var _entryKey = _step.value;
5454
+ if (!Object.prototype.hasOwnProperty.call(value, _entryKey)) {
5455
+ expanded.push({
5456
+ op: "remove",
5457
+ path: [_entryKey]
5458
+ });
5459
+ }
5460
+ }
5461
+ } catch (err) {
5462
+ _iterator.e(err);
5463
+ } finally {
5464
+ _iterator.f();
5465
+ }
5466
+ return fp.result.ok(expanded);
5467
+ }
5468
+
5469
+ /**
5470
+ * What a whole-record write says about ONE entry: the same rule
5471
+ * {@link expandJsonValuesRootOp} applies to all of them.
5472
+ *
5473
+ * For a reader that is about one entry. Expanding the whole record to find the
5474
+ * one op that names its key costs an op per entry per entry read - the record
5475
+ * squared, on exactly the large records this feature is for. The rule itself is
5476
+ * `writeForEntry`, shared with the expansion above, so the two cannot come to
5477
+ * disagree; only the loop around it differs.
5478
+ *
5479
+ * `null` means the write says nothing about this entry: it holds that content
5480
+ * already, or the record does not name it and it does not exist.
5481
+ */
5482
+ function expandJsonValuesRootOpForKey(op, entryKey, currentEntries) {
5483
+ var valueRes = jsonValuesRootValue(op);
5484
+ if (fp.result.isErr(valueRes)) {
5485
+ return valueRes;
5486
+ }
5487
+ var value = valueRes.value;
5488
+ if (Object.prototype.hasOwnProperty.call(value, entryKey)) {
5489
+ return fp.result.ok(writeForEntry(value, entryKey, currentEntries));
5490
+ }
5491
+ if (currentEntries.has(entryKey)) {
5492
+ return fp.result.ok({
5493
+ op: "remove",
5494
+ path: [entryKey]
5495
+ });
5496
+ }
5497
+ return fp.result.ok(null);
5498
+ }
5499
+
5500
+ /**
5501
+ * The record a whole-record write carries, once it is known to be one.
5502
+ *
5503
+ * Every entry's value is checked here rather than where each one is written:
5504
+ * a marker anywhere in the record means the writer handed over a module's
5505
+ * Source, and the answer to that is to refuse the write, not to write the rest
5506
+ * of it. Both entry points ask, so an entry read reports it too rather than
5507
+ * showing a draft of a patch that publishing will refuse.
5508
+ */
5509
+ function jsonValuesRootValue(op) {
5510
+ if (op.op !== "replace" && op.op !== "add") {
5511
+ return fp.result.err(new patch.PatchError("Cannot '".concat(op.op, "' the root of a .jsonValues() record: only add and replace write the whole record")));
5512
+ }
5513
+ var value = op.value;
5514
+ if (!isJsonValuesRecord(value)) {
5515
+ return fp.result.err(new patch.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"));
5516
+ }
5517
+ for (var _i2 = 0, _Object$keys2 = Object.keys(value); _i2 < _Object$keys2.length; _i2++) {
5518
+ var entryKey = _Object$keys2[_i2];
5519
+ /*
5520
+ * A marker is what a module's Source holds WHERE THE CONTENT IS NOT: it
5521
+ * says the content is in the entry's own file. Handed back as an entry's
5522
+ * value - which is what replaying an archived Source does - it would
5523
+ * replace that content with the note saying where it used to be, and
5524
+ * nothing downstream could tell the difference.
5525
+ */
5526
+ if (core.Internal.isJson(value[entryKey])) {
5527
+ return fp.result.err(new patch.PatchError("Cannot write the .jsonValues() entry '".concat(entryKey, "' from a '").concat(core.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")));
5528
+ }
5529
+ }
5530
+ return fp.result.ok(value);
5531
+ }
5532
+
5533
+ /** THE rule, for one entry the record names. `null` = nothing to write. */
5534
+ function writeForEntry(value, entryKey, currentEntries) {
5535
+ var content = value[entryKey];
5536
+ if (!currentEntries.has(entryKey)) {
5537
+ return {
5538
+ op: "add",
5539
+ path: [entryKey],
5540
+ value: content
5541
+ };
5542
+ }
5543
+ var current = currentEntries.get(entryKey);
5544
+ if (current !== undefined && patch.deepEqual(current, content)) {
5545
+ // Unchanged: emitting a replace here would rewrite the entry's file with
5546
+ // the bytes it already holds, so putting a module back would show up as a
5547
+ // change to every entry in it.
5548
+ return null;
5549
+ }
5550
+ return {
5551
+ op: "replace",
5552
+ path: [entryKey],
5553
+ value: content
5554
+ };
5555
+ }
5556
+
5557
+ /**
5558
+ * Rebases a patch op that targets a `.jsonValues()` entry's content so its paths
5559
+ * are relative to the entry's `*.val.json` root (drops the record + entry-key
5560
+ * prefix). Used to replay the op against the backing JSON file.
5561
+ */
5562
+ function rebaseContentOp(op, prefixLen) {
5563
+ var path = op.path.slice(prefixLen);
5564
+ switch (op.op) {
5565
+ case "add":
5566
+ case "replace":
5567
+ case "test":
5568
+ return fp.result.ok(createForOfIteratorHelper._objectSpread2(createForOfIteratorHelper._objectSpread2({}, op), {}, {
5569
+ path: path
5570
+ }));
5571
+ case "remove":
5572
+ {
5573
+ if (!fp.array.isNonEmpty(path)) {
5574
+ return fp.result.err(new patch.PatchError("Cannot remove the root of a jsonValues entry"));
5575
+ }
5576
+ return fp.result.ok(createForOfIteratorHelper._objectSpread2(createForOfIteratorHelper._objectSpread2({}, op), {}, {
5577
+ path: path
5578
+ }));
5579
+ }
5580
+ case "move":
5581
+ {
5582
+ var from = op.from.slice(prefixLen);
5583
+ if (!fp.array.isNonEmpty(from)) {
5584
+ return fp.result.err(new patch.PatchError("Cannot move from the root of a jsonValues entry"));
5585
+ }
5586
+ return fp.result.ok(createForOfIteratorHelper._objectSpread2(createForOfIteratorHelper._objectSpread2({}, op), {}, {
5587
+ path: path,
5588
+ from: from
5589
+ }));
5590
+ }
5591
+ case "copy":
5592
+ return fp.result.ok(createForOfIteratorHelper._objectSpread2(createForOfIteratorHelper._objectSpread2({}, op), {}, {
5593
+ path: path,
5594
+ from: op.from.slice(prefixLen)
5595
+ }));
5596
+ case "file":
5597
+ // A file op carries bytes, not content. The binary goes through the
5598
+ // normal upload pipeline; what lands in the entry is the `patch_id` that
5599
+ // marks the bytes as not yet committed, which the caller writes.
5600
+ return fp.result.err(new patch.PatchError("A file op is not a content op: rebase it via its path"));
5601
+ }
5602
+ }
5603
+
5604
+ /**
5605
+ * The entry a path names when it names a WHOLE entry of the root
5606
+ * `.jsonValues()` record, and `undefined` for anything else.
5607
+ */
5608
+ function wholeEntryKey(serializedSchema, path) {
5609
+ if (!serializedSchema) {
5610
+ return undefined;
5611
+ }
5612
+ var cls = classifyJsonValuesOp(serializedSchema, path);
5613
+ return cls.kind === "entry" && cls.recordPath.length === 0 && cls.subPath.length === 0 ? cls.entryKey : undefined;
5614
+ }
5615
+
5616
+ /*
5617
+ * THE engine: one function that turns a module's content and a patch into
5618
+ * the module's next content. See `docs/plans/one-content-model.md`, "One
5619
+ * engine".
5620
+ *
5621
+ * It owns the rules the server's two readers used to follow each in their own
5622
+ * way (`ValOps.getSources` for the module, and a replay of its own for a
5623
+ * `.jsonValues()` entry, kept test-only in `legacyEngine.ts` in
5624
+ * `@valbuild/server` for the conformance cases):
5625
+ *
5626
+ * - where a file's `patch_id` lands: the field's path plus `nestedFilePath`,
5627
+ * after every other op of the patch;
5628
+ * - what an edit inside an entry does: applied to the entry's content when it
5629
+ * is loaded, held (`waiting`) until it is when it is not;
5630
+ * - what a whole entry added does: a marker in the module, content in the
5631
+ * entry;
5632
+ * - that a module's chain stops at its first failing patch, and that the
5633
+ * entries stop with it.
5634
+ *
5635
+ * Copy-on-write along each op's path: nothing is mutated, so a subtree no op
5636
+ * touched keeps its identity (which makes "did this path change?" a `===`),
5637
+ * and a patch costs the depth of its path, not the module's size. Every
5638
+ * container it copies is counted ({@link engineWork}).
5639
+ */
5640
+
5641
+ /** What one `.jsonValues()` entry is at a point in a module's chain. */
5642
+
5643
+ /**
5644
+ * The ops held for an entry, newest first: a list that is extended without
5645
+ * copying, so a chain of N edits to an entry nobody loaded costs N, not N².
5646
+ */
5647
+
5648
+ /**
5649
+ * A module's content: its source, and what its `.jsonValues()` entries are.
5650
+ *
5651
+ * `entries` holds only the entries the chain has touched. An entry it has
5652
+ * not is the base's: `waiting` on its own key with nothing held, as long as
5653
+ * the source has its marker ({@link entryState}). So a module with no
5654
+ * entries, or a chain that never touches one, costs nothing here.
5655
+ */
5656
+
5657
+ /** One patch of a module's chain. */
5658
+
5659
+ var NO_ENTRIES = new Map();
5660
+
5661
+ /** A module's content with nothing applied yet: its base. */
5662
+ function baseModuleContent(source) {
5663
+ return {
5664
+ source: source,
5665
+ entries: NO_ENTRIES
5666
+ };
5667
+ }
5668
+
5669
+ // #region counted work
5670
+
5671
+ /**
5672
+ * What a copied container costs, per slot copied (an object's entry or an
5673
+ * array's element): one reference. Strings and numbers are shared, never
5674
+ * copied, so this is what a copy really moves -- and what the whole module
5675
+ * cost per patch before copy-on-write (`applyPatch(deepClone(module))`).
5676
+ */
5677
+ var SLOT_BYTES = 8;
5678
+ var applies = 0;
5679
+ var clonedBytes = 0;
5680
+
5681
+ /**
5682
+ * The work the engine has done in this process: patches applied
5683
+ * (`engine:apply-patch`) and bytes copied (`engine:clone-bytes`, see
5684
+ * {@link SLOT_BYTES}). A test takes it before and after; never reset, so two
5685
+ * readers of it cannot disturb each other.
5686
+ */
5687
+ function engineWork() {
5688
+ return {
5689
+ applyPatch: applies,
5690
+ cloneBytes: clonedBytes
5691
+ };
5692
+ }
5693
+
5694
+ // #region the patch
5695
+
5696
+ /**
5697
+ * Applies one patch to a module's content. Nothing is mutated: the module
5698
+ * passed in is still what it was, and the one returned shares every subtree
5699
+ * the patch did not touch.
5700
+ *
5701
+ * `failed` means the patch does not apply, and then none of it does: half a
5702
+ * patch is half an edit. The caller stops the module's chain there.
5703
+ *
5704
+ * `schema` is the module's serialized schema: it says which ops reach into a
5705
+ * `.jsonValues()` entry. Without one, every op is an op on the source.
5706
+ */
5707
+ function applyPatchRecord(module, record, schema) {
5708
+ applies++;
5709
+ var patchId = record.patchId;
5710
+ var fail = function fail(message) {
5711
+ return {
5712
+ status: "failed",
5713
+ patchId: patchId,
5714
+ message: message
5715
+ };
5716
+ };
5717
+ var source = module.source;
5718
+ var entries = null;
5719
+ var stateOf = function stateOf(key) {
5720
+ var _get;
5721
+ return (_get = (entries !== null && entries !== void 0 ? entries : module.entries).get(key)) !== null && _get !== void 0 ? _get : implicitState(source, key);
5722
+ };
5723
+ var setEntry = function setEntry(key, state) {
5724
+ if (entries === null) {
5725
+ clonedBytes += SLOT_BYTES * module.entries.size;
5726
+ entries = new Map(module.entries);
5727
+ }
5728
+ entries.set(key, state);
5729
+ };
5730
+ /** Sets it, unless the entry already failed: an entry's error is sticky. */
5731
+ var settle = function settle(key, state) {
5732
+ var _stateOf;
5733
+ if (((_stateOf = stateOf(key)) === null || _stateOf === void 0 ? void 0 : _stateOf.kind) !== "error") setEntry(key, state);
5734
+ };
5735
+ /**
5736
+ * The record's keys as they stand while this patch is walked, for a write
5737
+ * of the whole record to expand against. Only the key set: the source holds
5738
+ * markers, and an entry's content may not be loaded. Seeded from the source
5739
+ * BEFORE this patch and kept current by the whole-entry ops below.
5740
+ */
5741
+ var keys = null;
5742
+ var currentKeys = function currentKeys() {
5743
+ if (keys === null) {
5744
+ keys = new Map();
5745
+ if (isObjectNode(module.source)) {
5746
+ for (var _i = 0, _Object$keys = Object.keys(module.source); _i < _Object$keys.length; _i++) {
5747
+ var _key = _Object$keys[_i];
5748
+ keys.set(_key, undefined);
5749
+ }
5750
+ }
5751
+ }
5752
+ return keys;
5753
+ };
5754
+ /** Where a file's `patch_id` lands in the source; last write wins its place. */
5755
+ var fileMarks = new Map();
5756
+ /** And in an entry's content. */
5757
+ var entryFileMarks = [];
5758
+ var _iterator = createForOfIteratorHelper._createForOfIteratorHelper(record.patch),
5759
+ _step;
5760
+ try {
5761
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
5762
+ var rawOp = _step.value;
5763
+ /*
5764
+ * A write of the WHOLE record fans out into per-entry ops first, through
5765
+ * the expansion the commit flow uses too (`expandJsonValuesRootOp`).
5766
+ * Left as it is, it would put content where the source holds markers.
5767
+ */
5768
+ var ops = [rawOp];
5769
+ if (schema !== undefined && isJsonValuesRootOp(schema, rawOp)) {
5770
+ var expanded = expandJsonValuesRootOp(rawOp, currentKeys());
5771
+ if (fp.result.isErr(expanded)) return fail(expanded.error.message);
5772
+ ops = expanded.value;
5773
+ }
5774
+ var _iterator3 = createForOfIteratorHelper._createForOfIteratorHelper(ops),
5775
+ _step3;
5776
+ try {
5777
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
5778
+ var _op = _step3.value;
5779
+ var cls = schema === undefined ? {
5780
+ kind: "normal"
5781
+ } : classifyJsonValuesOp(schema, _op.path);
5782
+ if (_op.op === "file") {
5783
+ var _op$nestedFilePath;
5784
+ /*
5785
+ * A file op carries bytes, not content. What the content needs is
5786
+ * the `patch_id` saying those bytes are not published yet, so the
5787
+ * file is served from the patch rather than from a `/public` path
5788
+ * that holds nothing. Keyed by where it LANDS -- the field's path
5789
+ * plus `nestedFilePath` -- because a video's poster and captions are
5790
+ * file ops on the video's own path. A delete has no bytes to point at.
5791
+ */
5792
+ if (_op.value === null) continue;
5793
+ var nested = (_op$nestedFilePath = _op.nestedFilePath) !== null && _op$nestedFilePath !== void 0 ? _op$nestedFilePath : [];
5794
+ if (cls.kind === "entry" && cls.subPath.length > 0) {
5795
+ if (cls.recordPath.length === 0) {
5796
+ entryFileMarks.push({
5797
+ key: cls.entryKey,
5798
+ path: cls.subPath.concat(nested, "patch_id")
5799
+ });
5800
+ }
5801
+ continue;
5802
+ }
5803
+ var at = _op.path.concat(nested, "patch_id");
5804
+ fileMarks.set(JSON.stringify(at), at);
5805
+ if (cls.kind === "entry" && cls.recordPath.length === 0) {
5806
+ // The entry IS the media: its content gets the mark too.
5807
+ entryFileMarks.push({
5808
+ key: cls.entryKey,
5809
+ path: nested.concat("patch_id")
5810
+ });
5811
+ }
5812
+ continue;
5813
+ }
5814
+ if (cls.kind === "normal") {
5815
+ var _applied = applyOp(source, _op);
5816
+ if (fp.result.isErr(_applied)) return fail(_applied.error.message);
5817
+ source = _applied.value;
5818
+ if (_op.op === "move") {
5819
+ var fromKey = wholeEntryKey(schema, _op.from);
5820
+ if (fromKey !== undefined) settle(fromKey, {
5821
+ kind: "deleted"
5822
+ });
5823
+ }
5824
+ if (_op.path.length === 0 && (schema === null || schema === void 0 ? void 0 : schema.type) === "record" && schema.jsonValues === true) {
5825
+ // The record itself was replaced (by something that is not a
5826
+ // record): every entry went with it.
5827
+ clonedBytes += SLOT_BYTES * (entries !== null && entries !== void 0 ? entries : module.entries).size;
5828
+ entries = new Map();
5829
+ }
5830
+ continue;
5831
+ }
5832
+ if (cls.recordPath.length > 0) {
5833
+ return fail("Nested .jsonValues() records are not supported: '".concat(cls.recordPath.join("."), "'. Use .jsonValues() only on a module's root record/router."));
5834
+ }
5835
+ var _key3 = cls.entryKey;
5836
+ if (cls.subPath.length > 0) {
5837
+ // An edit INSIDE an entry: its content, never the source's marker.
5838
+ if (_op.op === "move" || _op.op === "copy") {
5839
+ var fromCls = schema === undefined ? {
5840
+ kind: "normal"
5841
+ } : classifyJsonValuesOp(schema, _op.from);
5842
+ if (fromCls.kind !== "entry" || fromCls.entryKey !== _key3) {
5843
+ return fail("Cannot '".concat(_op.op, "' between different jsonValues entries"));
5844
+ }
5845
+ }
5846
+ var rebased = rebaseContentOp(_op, cls.recordPath.length + 1);
5847
+ if (fp.result.isErr(rebased)) return fail(rebased.error.message);
5848
+ var _state = stateOf(_key3);
5849
+ if (_state === undefined || _state.kind === "deleted") {
5850
+ setEntry(_key3, {
5851
+ kind: "error",
5852
+ patchId: patchId,
5853
+ message: "Cannot edit jsonValues entry '".concat(_key3, "': it does not exist")
5854
+ });
5855
+ } else if (_state.kind === "waiting") {
5856
+ setEntry(_key3, {
5857
+ kind: "waiting",
5858
+ from: _state.from,
5859
+ ops: {
5860
+ op: rebased.value,
5861
+ patchId: patchId,
5862
+ before: _state.ops
5863
+ }
5864
+ });
5865
+ } else if (_state.kind === "content") {
5866
+ var _applied2 = applyOp(_state.content, rebased.value);
5867
+ setEntry(_key3, fp.result.isErr(_applied2) ? {
5868
+ kind: "error",
5869
+ patchId: patchId,
5870
+ message: _applied2.error.message
5871
+ } : {
5872
+ kind: "content",
5873
+ content: _applied2.value
5874
+ });
5875
+ }
5876
+ continue;
5877
+ }
5878
+
5879
+ // A WHOLE entry: a marker in the source, content in the entry.
5880
+ var marker = createForOfIteratorHelper._defineProperty(createForOfIteratorHelper._defineProperty({}, core.VAL_EXTENSION, "json"), "patch_id", patchId);
5881
+ if (_op.op === "add" || _op.op === "replace") {
5882
+ var written = _op.op === "add" ? addAt(source, _op.path, marker) : replaceAt(source, _op.path, marker);
5883
+ if (fp.result.isErr(written)) return fail(written.error.message);
5884
+ source = written.value;
5885
+ currentKeys().set(_key3, undefined);
5886
+ settle(_key3, {
5887
+ kind: "content",
5888
+ content: _op.value
5889
+ });
5890
+ } else if (_op.op === "remove") {
5891
+ var removed = removeAt(source, _op.path);
5892
+ if (fp.result.isErr(removed)) return fail(removed.error.message);
5893
+ source = removed.value.doc;
5894
+ currentKeys()["delete"](_key3);
5895
+ settle(_key3, {
5896
+ kind: "deleted"
5897
+ });
5898
+ } else if (_op.op === "move" || _op.op === "copy") {
5899
+ var _fromCls = schema === undefined ? {
5900
+ kind: "normal"
5901
+ } : classifyJsonValuesOp(schema, _op.from);
5902
+ if (_fromCls.kind !== "entry" || _fromCls.subPath.length !== 0 || _fromCls.recordPath.length !== 0) {
5903
+ 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("."), "')"));
5904
+ }
5905
+ var _fromKey = _fromCls.entryKey;
5906
+ // Read before anything moves: the destination is the source entry
5907
+ // as it stood at this op.
5908
+ var fromState = stateOf(_fromKey);
5909
+ var added = addAt(source, _op.path, marker);
5910
+ if (fp.result.isErr(added)) return fail(added.error.message);
5911
+ source = added.value;
5912
+ currentKeys().set(_key3, undefined);
5913
+ if (_op.op === "move") {
5914
+ var _removed = removeAt(source, _op.from);
5915
+ if (fp.result.isErr(_removed)) return fail(_removed.error.message);
5916
+ source = _removed.value.doc;
5917
+ currentKeys()["delete"](_fromKey);
5918
+ }
5919
+ settle(_key3, fromState === undefined || fromState.kind === "deleted" ? {
5920
+ kind: "error",
5921
+ patchId: patchId,
5922
+ message: "Cannot resolve '".concat(_op.op, "' of jsonValues entry '").concat(_key3, "' from its own content")
5923
+ } : fromState);
5924
+ if (_op.op === "move" && _fromKey !== _key3) {
5925
+ settle(_fromKey, {
5926
+ kind: "deleted"
5927
+ });
5928
+ }
5929
+ } else {
5930
+ // `test` of a whole entry: the commit refuses it (`ValOps.prepare`),
5931
+ // so the read does too, rather than showing a patch publishing fails.
5932
+ return fail("Unsupported op '".concat(_op.op, "' on jsonValues entry '").concat(_key3, "' (supported: add, remove, replace, move, copy)"));
5933
+ }
5934
+ }
5935
+ } catch (err) {
5936
+ _iterator3.e(err);
5937
+ } finally {
5938
+ _iterator3.f();
5939
+ }
5940
+ }
5941
+ } catch (err) {
5942
+ _iterator.e(err);
5943
+ } finally {
5944
+ _iterator.f();
5945
+ }
5946
+ var _iterator2 = createForOfIteratorHelper._createForOfIteratorHelper(fileMarks.values()),
5947
+ _step2;
5948
+ try {
5949
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
5950
+ var _at = _step2.value;
5951
+ var marked = addAt(source, _at, patchId);
5952
+ if (fp.result.isErr(marked)) return fail(marked.error.message);
5953
+ source = marked.value;
5954
+ }
5955
+ } catch (err) {
5956
+ _iterator2.e(err);
5957
+ } finally {
5958
+ _iterator2.f();
5959
+ }
5960
+ for (var _i2 = 0, _entryFileMarks = entryFileMarks; _i2 < _entryFileMarks.length; _i2++) {
5961
+ var _entryFileMarks$_i = _entryFileMarks[_i2],
5962
+ _key2 = _entryFileMarks$_i.key,
5963
+ path = _entryFileMarks$_i.path;
5964
+ var state = stateOf(_key2);
5965
+ var op = {
5966
+ op: "add",
5967
+ path: path,
5968
+ value: patchId
5969
+ };
5970
+ if ((state === null || state === void 0 ? void 0 : state.kind) === "content") {
5971
+ var applied = applyOp(state.content, op);
5972
+ setEntry(_key2, fp.result.isErr(applied) ? {
5973
+ kind: "error",
5974
+ patchId: patchId,
5975
+ message: applied.error.message
5976
+ } : {
5977
+ kind: "content",
5978
+ content: applied.value
5979
+ });
5980
+ } else if ((state === null || state === void 0 ? void 0 : state.kind) === "waiting") {
5981
+ setEntry(_key2, {
5982
+ kind: "waiting",
5983
+ from: state.from,
5984
+ ops: {
5985
+ op: op,
5986
+ patchId: patchId,
5987
+ before: state.ops
5988
+ }
5989
+ });
5990
+ }
5991
+ }
5992
+ return {
5993
+ status: "applied",
5994
+ module: {
5995
+ source: source,
5996
+ entries: entries !== null && entries !== void 0 ? entries : module.entries
5997
+ }
5998
+ };
5999
+ }
6000
+
6001
+ /**
6002
+ * A module's chain: every patch in order, stopping at the first that does
6003
+ * not apply. `applied` is how many did, so the chain the content is AT is
6004
+ * the first `applied` ids -- what its version names.
6005
+ */
6006
+ function applyChain(module, records, schema) {
6007
+ var current = module;
6008
+ var _iterator4 = createForOfIteratorHelper._createForOfIteratorHelper(records.entries()),
6009
+ _step4;
6010
+ try {
6011
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
6012
+ var _step4$value = createForOfIteratorHelper._slicedToArray(_step4.value, 2),
6013
+ index = _step4$value[0],
6014
+ record = _step4$value[1];
6015
+ var res = applyPatchRecord(current, record, schema);
6016
+ if (res.status === "failed") {
6017
+ return {
6018
+ module: current,
6019
+ applied: index,
6020
+ failed: {
6021
+ patchId: res.patchId,
6022
+ message: res.message
6023
+ }
6024
+ };
6025
+ }
6026
+ current = res.module;
6027
+ }
6028
+ } catch (err) {
6029
+ _iterator4.e(err);
6030
+ } finally {
6031
+ _iterator4.f();
6032
+ }
6033
+ return {
6034
+ module: current,
6035
+ applied: records.length,
6036
+ failed: null
6037
+ };
6038
+ }
6039
+
6040
+ // #region entries
6041
+
6042
+ /**
6043
+ * What an entry is in this module: what the chain made it, or -- for one the
6044
+ * chain never touched -- the base's, if the source has its marker.
6045
+ */
6046
+ function entryState(module, key) {
6047
+ var _module$entries$get;
6048
+ return (_module$entries$get = module.entries.get(key)) !== null && _module$entries$get !== void 0 ? _module$entries$get : implicitState(module.source, key);
6049
+ }
6050
+ function implicitState(source, key) {
6051
+ return isObjectNode(source) && Object.prototype.hasOwnProperty.call(source, key) && core.Internal.isJson(source[key]) ? {
6052
+ kind: "waiting",
6053
+ from: key,
6054
+ ops: null
6055
+ } : undefined;
6056
+ }
6057
+
6058
+ /** An entry's content, read. */
6059
+
6060
+ /**
6061
+ * An entry's content: what {@link entryState} says, with what it was waiting
6062
+ * for applied. `baseContentOf` is the BASE content of an entry by key (its
6063
+ * `*.val.json`, or what a save left there), `undefined` for one the base does
6064
+ * not have.
6065
+ */
6066
+ function resolveEntry(key, state, baseContentOf) {
6067
+ if (state === undefined || state.kind === "deleted") {
6068
+ return {
6069
+ kind: "missing"
6070
+ };
6071
+ }
6072
+ if (state.kind === "error") {
6073
+ return {
6074
+ kind: "error",
6075
+ message: state.message,
6076
+ patchId: state.patchId
6077
+ };
6078
+ }
6079
+ if (state.kind === "content") {
6080
+ return {
6081
+ kind: "content",
6082
+ content: state.content
6083
+ };
6084
+ }
6085
+ var held = [];
6086
+ for (var at = state.ops; at !== null; at = at.before) held.push(at);
6087
+ var content = baseContentOf(state.from);
6088
+ if (content === undefined) {
6089
+ var first = held[held.length - 1];
6090
+ return first === undefined ? {
6091
+ kind: "missing"
6092
+ } : {
6093
+ kind: "error",
6094
+ message: "Cannot edit jsonValues entry '".concat(key, "': it does not exist"),
6095
+ patchId: first.patchId
6096
+ };
6097
+ }
6098
+ for (var i = held.length - 1; i >= 0; i--) {
6099
+ var _held$i = held[i],
6100
+ op = _held$i.op,
6101
+ patchId = _held$i.patchId;
6102
+ var applied = applyOp(content, op);
6103
+ if (fp.result.isErr(applied)) {
6104
+ return {
6105
+ kind: "error",
6106
+ message: applied.error.message,
6107
+ patchId: patchId
6108
+ };
6109
+ }
6110
+ content = applied.value;
6111
+ }
6112
+ return {
6113
+ kind: "content",
6114
+ content: content
6115
+ };
6116
+ }
6117
+
6118
+ // #region copy-on-write JSON ops
6119
+
6120
+ /*
6121
+ * RFC 6902 as `JSONOps` (in `@valbuild/core/patch`) applies it -- the same
6122
+ * results and the same error messages, which the conformance cases hold --
6123
+ * but without mutating anything: a container on an op's path is copied, and
6124
+ * everything else is shared.
6125
+ */
6126
+
6127
+ function isArrayNode(node) {
6128
+ return Array.isArray(node);
6129
+ }
6130
+ function isObjectNode(node) {
6131
+ return createForOfIteratorHelper._typeof(node) === "object" && node !== null && !Array.isArray(node);
6132
+ }
6133
+ function hasOwn(node, key) {
6134
+ return Object.prototype.hasOwnProperty.call(node, key);
6135
+ }
6136
+ function patchError(message) {
6137
+ return fp.result.err(new patch.PatchError(message));
6138
+ }
6139
+ function copyArray(node) {
6140
+ clonedBytes += SLOT_BYTES * node.length;
6141
+ return node.slice();
6142
+ }
6143
+
6144
+ /**
6145
+ * A shallow copy, key by key. Not a spread, which was tried: a module's root
6146
+ * is a large object in dictionary mode, and V8 spreads one of forty thousand
6147
+ * keys at about half this speed (the 5 MB keystroke of
6148
+ * `scenarios/measure/keystroke.ts` went from 19 ms to 33).
6149
+ */
6150
+ function copyObject(node) {
6151
+ var copy = {};
6152
+ var slots = 0;
6153
+ for (var _i3 = 0, _Object$keys2 = Object.keys(node); _i3 < _Object$keys2.length; _i3++) {
6154
+ var _key4 = _Object$keys2[_i3];
6155
+ setOwn(copy, _key4, node[_key4]);
6156
+ slots++;
6157
+ }
6158
+ clonedBytes += SLOT_BYTES * slots;
6159
+ return copy;
6160
+ }
6161
+
6162
+ /** An own property, even for `__proto__`, which `=` would not create. */
6163
+ function setOwn(target, key, value) {
6164
+ if (key === "__proto__") {
6165
+ Object.defineProperty(target, key, {
6166
+ value: value,
6167
+ writable: true,
6168
+ enumerable: true,
6169
+ configurable: true
6170
+ });
6171
+ } else {
6172
+ target[key] = value;
6173
+ }
6174
+ }
6175
+ function arrayIndex(key, node, inclusive) {
6176
+ if (inclusive && key === "-") return fp.result.ok(node.length);
6177
+ var index = patch.parseAndValidateArrayIndex(key);
6178
+ if (fp.result.isErr(index)) return index;
6179
+ return (inclusive ? index.value <= node.length : index.value < node.length) ? index : patchError("Array index out of bounds");
6180
+ }
6181
+ function childOf(node, key) {
6182
+ if (isArrayNode(node)) {
6183
+ var index = patch.parseAndValidateArrayIndex(key);
6184
+ if (fp.result.isErr(index)) return index;
6185
+ return index.value >= node.length ? patchError("Array index out of bounds") : fp.result.ok(node[index.value]);
6186
+ }
6187
+ if (isObjectNode(node)) {
6188
+ return fp.result.ok(hasOwn(node, key) ? node[key] : undefined);
6189
+ }
6190
+ return patchError("Cannot access non-object/array");
6191
+ }
6192
+
6193
+ /** `node` with `key` set to `value`: the one copy a step on a path costs. */
6194
+ function withChild(node, key, value) {
6195
+ if (isArrayNode(node)) {
6196
+ var copy = copyArray(node);
6197
+ copy[Number(key)] = value;
6198
+ return copy;
6199
+ }
6200
+ if (isObjectNode(node)) {
6201
+ var _copy = copyObject(node);
6202
+ setOwn(_copy, key, value);
6203
+ return _copy;
6204
+ }
6205
+ return node;
6206
+ }
6207
+
6208
+ /**
6209
+ * Walks `path` to the parent of its last segment, lets `leaf` change that
6210
+ * parent, and copies every container on the way back up. Errors as
6211
+ * `JSONOps`'s pointer walk: the first missing step is reported.
6212
+ */
6213
+ function updateAt(node, path, index, leaf) {
6214
+ var key = path[index];
6215
+ if (index === path.length - 1) return leaf(node, key);
6216
+ var child = childOf(node, key);
6217
+ if (fp.result.isErr(child)) return child;
6218
+ if (child.value === undefined) {
6219
+ return patchError("Path refers to non-existing object/array");
6220
+ }
6221
+ var updated = updateAt(child.value, path, index + 1, leaf);
6222
+ if (fp.result.isErr(updated)) return updated;
6223
+ return fp.result.ok({
6224
+ node: updated.value.node === child.value ? node : withChild(node, key, updated.value.node),
6225
+ out: updated.value.out
6226
+ });
6227
+ }
6228
+ function isNonEmpty(path) {
6229
+ return path.length > 0;
6230
+ }
6231
+ function getAt(doc, path) {
6232
+ var node = doc;
6233
+ var _iterator5 = createForOfIteratorHelper._createForOfIteratorHelper(path),
6234
+ _step5;
6235
+ try {
6236
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
6237
+ var _key5 = _step5.value;
6238
+ var child = childOf(node, _key5);
6239
+ if (fp.result.isErr(child)) return child;
6240
+ if (child.value === undefined) {
6241
+ return patchError("Path refers to non-existing object/array");
6242
+ }
6243
+ node = child.value;
6244
+ }
6245
+ } catch (err) {
6246
+ _iterator5.e(err);
6247
+ } finally {
6248
+ _iterator5.f();
6249
+ }
6250
+ return fp.result.ok(node);
6251
+ }
6252
+ function addAt(doc, path, value) {
6253
+ if (!isNonEmpty(path)) return fp.result.ok(value);
6254
+ var res = updateAt(doc, path, 0, function (parent, key) {
6255
+ if (isArrayNode(parent)) {
6256
+ var index = arrayIndex(key, parent, true);
6257
+ if (fp.result.isErr(index)) return index;
6258
+ var copy = copyArray(parent);
6259
+ copy.splice(index.value, 0, value);
6260
+ return fp.result.ok({
6261
+ node: copy,
6262
+ out: null
6263
+ });
6264
+ }
6265
+ if (isObjectNode(parent)) {
6266
+ var _copy2 = copyObject(parent);
6267
+ setOwn(_copy2, key, value);
6268
+ return fp.result.ok({
6269
+ node: _copy2,
6270
+ out: null
6271
+ });
6272
+ }
6273
+ return patchError("Cannot add to non-object/array");
6274
+ });
6275
+ return fp.result.isErr(res) ? res : fp.result.ok(res.value.node);
6276
+ }
6277
+ function replaceAt(doc, path, value) {
6278
+ if (!isNonEmpty(path)) return fp.result.ok(value);
6279
+ var res = updateAt(doc, path, 0, function (parent, key) {
6280
+ if (isArrayNode(parent)) {
6281
+ var index = arrayIndex(key, parent, false);
6282
+ if (fp.result.isErr(index)) return index;
6283
+ var copy = copyArray(parent);
6284
+ copy[index.value] = value;
6285
+ return fp.result.ok({
6286
+ node: copy,
6287
+ out: null
6288
+ });
6289
+ }
6290
+ if (isObjectNode(parent)) {
6291
+ if (!hasOwn(parent, key)) {
6292
+ return patchError("Cannot replace object element which does not exist");
6293
+ }
6294
+ var _copy3 = copyObject(parent);
6295
+ setOwn(_copy3, key, value);
6296
+ return fp.result.ok({
6297
+ node: _copy3,
6298
+ out: null
6299
+ });
6300
+ }
6301
+ return patchError("Cannot replace in non-object/array");
6302
+ });
6303
+ return fp.result.isErr(res) ? res : fp.result.ok(res.value.node);
6304
+ }
6305
+ function removeAt(doc, path) {
6306
+ if (!isNonEmpty(path)) {
6307
+ return patchError("Cannot remove from non-object/array");
6308
+ }
6309
+ var res = updateAt(doc, path, 0, function (parent, key) {
6310
+ if (isArrayNode(parent)) {
6311
+ var index = arrayIndex(key, parent, false);
6312
+ if (fp.result.isErr(index)) return index;
6313
+ var copy = copyArray(parent);
6314
+ var _copy$splice = copy.splice(index.value, 1),
6315
+ _copy$splice2 = createForOfIteratorHelper._slicedToArray(_copy$splice, 1),
6316
+ removed = _copy$splice2[0];
6317
+ return fp.result.ok({
6318
+ node: copy,
6319
+ out: removed
6320
+ });
6321
+ }
6322
+ if (isObjectNode(parent) && hasOwn(parent, key)) {
6323
+ var _copy4 = copyObject(parent);
6324
+ var _removed2 = parent[key];
6325
+ delete _copy4[key];
6326
+ return fp.result.ok({
6327
+ node: _copy4,
6328
+ out: _removed2
6329
+ });
6330
+ }
6331
+ return patchError("Cannot remove from non-object/array");
6332
+ });
6333
+ return fp.result.isErr(res) ? res : fp.result.ok({
6334
+ doc: res.value.node,
6335
+ removed: res.value.out
6336
+ });
6337
+ }
6338
+
6339
+ /** One op on a document. `file` is not an op on content: the caller marks. */
6340
+ function applyOp(doc, op) {
6341
+ switch (op.op) {
6342
+ case "add":
6343
+ return addAt(doc, op.path, op.value);
6344
+ case "replace":
6345
+ return replaceAt(doc, op.path, op.value);
6346
+ case "remove":
6347
+ {
6348
+ var removed = removeAt(doc, op.path);
6349
+ return fp.result.isErr(removed) ? removed : fp.result.ok(removed.value.doc);
6350
+ }
6351
+ case "move":
6352
+ {
6353
+ var _removed3 = removeAt(doc, op.from);
6354
+ if (fp.result.isErr(_removed3)) return _removed3;
6355
+ return addAt(_removed3.value.doc, op.path, _removed3.value.removed);
6356
+ }
6357
+ case "copy":
6358
+ {
6359
+ // Shared, not cloned: nothing here mutates, so two places can hold one
6360
+ // value.
6361
+ var value = getAt(doc, op.from);
6362
+ if (fp.result.isErr(value)) return value;
6363
+ return addAt(doc, op.path, value.value);
6364
+ }
6365
+ case "test":
6366
+ {
6367
+ // As `applyPatch` in `@valbuild/core/patch`, which the commit applies
6368
+ // `.val.ts` text through: a path that does not exist, or a value there
6369
+ // that is not deeply equal, fails the patch (RFC 6902, 4.6). The two
6370
+ // have to agree, or a draft shows a patch Publish refuses.
6371
+ var _value = getAt(doc, op.path);
6372
+ if (fp.result.isErr(_value)) return _value;
6373
+ if (!patch.deepEqual(op.value, _value.value)) return patchError("Test failed");
6374
+ return fp.result.ok(doc);
6375
+ }
6376
+ case "file":
6377
+ return patchError("Cannot apply a file patch here");
6378
+ }
6379
+ }
6380
+
6381
+ /*
6382
+ * The store: modules by address. See `docs/plans/one-content-model.md`,
6383
+ * "The store: one address, immutable values, tiers".
6384
+ *
6385
+ * Content at a version never changes, so a store of modules by `moduleKey`
6386
+ * needs no invalidation, only eviction. A base is a version with the empty
6387
+ * chain, so the same store holds both. Every place that keeps modules uses
6388
+ * the same tiers, in the order that fits it: Val's server keeps
6389
+ * `[memoryTier, bundleTier, …]` in front of its head (`ValHead` in
6390
+ * `@valbuild/server`), and the Studio's will put an origin tier behind them
6391
+ * (phase 7).
6392
+ */
6393
+
6394
+ /** One module's content at one version, as a tier holds it. */
6395
+
6396
+ // #region counted work
6397
+
6398
+ var tierHits = new Map();
6399
+ var tierMisses = 0;
6400
+
6401
+ /**
6402
+ * What the stores in this process have answered, by tier, and how often
6403
+ * none of them had a key: a test takes it before and after. Never reset.
6404
+ */
6405
+ function storeWork() {
6406
+ return {
6407
+ hits: Object.fromEntries(tierHits),
6408
+ misses: tierMisses
6409
+ };
6410
+ }
6411
+
6412
+ // #region tiered
6413
+
6414
+ /**
6415
+ * Asks each tier in turn; what a slower tier answers is written to the faster
6416
+ * ones before it. A tier that answers `null` has no say.
6417
+ */
6418
+ function tiered(tiers) {
6419
+ return {
6420
+ name: tiers.map(function (tier) {
6421
+ return tier.name;
6422
+ }).join(">"),
6423
+ get: function get(key) {
6424
+ return createForOfIteratorHelper._asyncToGenerator(/*#__PURE__*/createForOfIteratorHelper._regenerator().m(function _callee() {
6425
+ var _iterator, _step, _tierHits$get, _step$value, index, tier, found, _iterator2, _step2, _faster$put, faster, _t;
6426
+ return createForOfIteratorHelper._regenerator().w(function (_context) {
6427
+ while (1) switch (_context.p = _context.n) {
6428
+ case 0:
6429
+ _iterator = createForOfIteratorHelper._createForOfIteratorHelper(tiers.entries());
6430
+ _context.p = 1;
6431
+ _iterator.s();
6432
+ case 2:
6433
+ if ((_step = _iterator.n()).done) {
6434
+ _context.n = 6;
6435
+ break;
6436
+ }
6437
+ _step$value = createForOfIteratorHelper._slicedToArray(_step.value, 2), index = _step$value[0], tier = _step$value[1];
6438
+ _context.n = 3;
6439
+ return tier.get(key);
6440
+ case 3:
6441
+ found = _context.v;
6442
+ if (!(found === null)) {
6443
+ _context.n = 4;
6444
+ break;
6445
+ }
6446
+ return _context.a(3, 5);
6447
+ case 4:
6448
+ tierHits.set(tier.name, ((_tierHits$get = tierHits.get(tier.name)) !== null && _tierHits$get !== void 0 ? _tierHits$get : 0) + 1);
6449
+ _iterator2 = createForOfIteratorHelper._createForOfIteratorHelper(tiers.slice(0, index));
6450
+ try {
6451
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
6452
+ faster = _step2.value;
6453
+ (_faster$put = faster.put) === null || _faster$put === void 0 || _faster$put.call(faster, key, found, found.bytes);
6454
+ }
6455
+ } catch (err) {
6456
+ _iterator2.e(err);
6457
+ } finally {
6458
+ _iterator2.f();
6459
+ }
6460
+ return _context.a(2, found);
6461
+ case 5:
6462
+ _context.n = 2;
6463
+ break;
6464
+ case 6:
6465
+ _context.n = 8;
6466
+ break;
6467
+ case 7:
6468
+ _context.p = 7;
6469
+ _t = _context.v;
6470
+ _iterator.e(_t);
6471
+ case 8:
6472
+ _context.p = 8;
6473
+ _iterator.f();
6474
+ return _context.f(8);
6475
+ case 9:
6476
+ tierMisses++;
6477
+ return _context.a(2, null);
6478
+ }
6479
+ }, _callee, null, [[1, 7, 8, 9]]);
6480
+ }))();
6481
+ },
6482
+ put: function put(key, value, bytes) {
6483
+ var _tiers$, _tiers$$put;
6484
+ (_tiers$ = tiers[0]) === null || _tiers$ === void 0 || (_tiers$$put = _tiers$.put) === null || _tiers$$put === void 0 || _tiers$$put.call(_tiers$, key, value, bytes);
6485
+ }
6486
+ };
6487
+ }
6488
+
6489
+ // #region memory
6490
+
6491
+ /**
6492
+ * Bounded by bytes; keeps the last `lastPerModule` versions per module path
6493
+ * (and, apart from them, the last `lastPerModule` bases), and lets go of the least recently read value when over `budgetBytes`. A
6494
+ * value bigger than the whole budget is still held while it is the newest,
6495
+ * so the read that made it is answered; the next put lets it go.
6496
+ */
6497
+ function memoryTier(opts) {
6498
+ var _opts$name;
6499
+ /** In least-recently-read order: a read moves a key to the end. */
6500
+ var held = new Map();
6501
+ /** Per module, its keys oldest first. */
6502
+ var rings = new Map();
6503
+ var bytes = 0;
6504
+ var evicted = 0;
6505
+ var versionOf = function versionOf(key) {
6506
+ var _held$get;
6507
+ return (_held$get = held.get(key)) === null || _held$get === void 0 ? void 0 : _held$get.value.version;
6508
+ };
6509
+ var drop = function drop(key) {
6510
+ var entry = held.get(key);
6511
+ if (entry === undefined) return;
6512
+ held["delete"](key);
6513
+ bytes -= entry.bytes;
6514
+ evicted++;
6515
+ var ring = rings.get(entry.value.path);
6516
+ if (ring !== undefined) {
6517
+ var at = ring.indexOf(key);
6518
+ if (at >= 0) ring.splice(at, 1);
6519
+ if (ring.length === 0) rings["delete"](entry.value.path);
6520
+ }
6521
+ };
6522
+ return {
6523
+ name: (_opts$name = opts.name) !== null && _opts$name !== void 0 ? _opts$name : "memory",
6524
+ get: function get(key) {
6525
+ var entry = held.get(key);
6526
+ if (entry === undefined) return null;
6527
+ held["delete"](key);
6528
+ held.set(key, entry);
6529
+ return entry.value;
6530
+ },
6531
+ put: function put(key, value, valueBytes) {
6532
+ var _rings$get;
6533
+ if (held.has(key)) {
6534
+ drop(key);
6535
+ evicted--;
6536
+ }
6537
+ held.set(key, {
6538
+ value: value,
6539
+ bytes: valueBytes
6540
+ });
6541
+ bytes += valueBytes;
6542
+ var ring = (_rings$get = rings.get(value.path)) !== null && _rings$get !== void 0 ? _rings$get : [];
6543
+ ring.push(key);
6544
+ rings.set(value.path, ring);
6545
+ // The last N versions, and apart from them the last N bases: a base
6546
+ // is what a slower tier answered (the bundle's own object, held
6547
+ // anyway) and written up here, and it must not push out the version
6548
+ // before the newest -- which a reader of the previous version still
6549
+ // asks for.
6550
+ var isBase = value.version.chain === EMPTY_CHAIN;
6551
+ var sameKind = ring.filter(function (held) {
6552
+ var _versionOf;
6553
+ return ((_versionOf = versionOf(held)) === null || _versionOf === void 0 ? void 0 : _versionOf.chain) === EMPTY_CHAIN === isBase;
6554
+ });
6555
+ var _iterator3 = createForOfIteratorHelper._createForOfIteratorHelper(sameKind.slice(0, Math.max(0, sameKind.length - opts.lastPerModule))),
6556
+ _step3;
6557
+ try {
6558
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
6559
+ var oldest = _step3.value;
6560
+ drop(oldest);
6561
+ }
6562
+ } catch (err) {
6563
+ _iterator3.e(err);
6564
+ } finally {
6565
+ _iterator3.f();
6566
+ }
6567
+ var _iterator4 = createForOfIteratorHelper._createForOfIteratorHelper(held.keys()),
6568
+ _step4;
6569
+ try {
6570
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
6571
+ var lru = _step4.value;
6572
+ if (bytes <= opts.budgetBytes) break;
6573
+ if (lru === key) continue;
6574
+ drop(lru);
6575
+ }
6576
+ } catch (err) {
6577
+ _iterator4.e(err);
6578
+ } finally {
6579
+ _iterator4.f();
6580
+ }
6581
+ },
6582
+ charge: function charge(key, extra) {
6583
+ var entry = held.get(key);
6584
+ if (entry === undefined) return;
6585
+ entry.bytes += extra;
6586
+ bytes += extra;
6587
+ },
6588
+ latest: function latest(path) {
6589
+ var _held$get$value, _held$get2;
6590
+ var ring = rings.get(path);
6591
+ var newest = ring === null || ring === void 0 ? void 0 : ring[ring.length - 1];
6592
+ 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;
6593
+ },
6594
+ stats: function stats() {
6595
+ // Versions: what a chain made. A base written up from a slower tier is
6596
+ // the build's own object (counted at no bytes), held beside them, and
6597
+ // not one of the module's versions.
6598
+ var mostVersions = 0;
6599
+ var mostBytes = 0;
6600
+ var versions = 0;
6601
+ var modules = 0;
6602
+ var _iterator5 = createForOfIteratorHelper._createForOfIteratorHelper(rings.values()),
6603
+ _step5;
6604
+ try {
6605
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
6606
+ var ring = _step5.value;
6607
+ var made = ring.filter(function (key) {
6608
+ var _versionOf2;
6609
+ return ((_versionOf2 = versionOf(key)) === null || _versionOf2 === void 0 ? void 0 : _versionOf2.chain) !== EMPTY_CHAIN;
6610
+ });
6611
+ versions += made.length;
6612
+ if (made.length > 0) modules++;
6613
+ mostVersions = Math.max(mostVersions, made.length);
6614
+ var moduleBytes = 0;
6615
+ var _iterator6 = createForOfIteratorHelper._createForOfIteratorHelper(ring),
6616
+ _step6;
6617
+ try {
6618
+ for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
6619
+ var _held$get$bytes, _held$get3;
6620
+ var _key = _step6.value;
6621
+ 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;
6622
+ }
6623
+ } catch (err) {
6624
+ _iterator6.e(err);
6625
+ } finally {
6626
+ _iterator6.f();
6627
+ }
6628
+ mostBytes = Math.max(mostBytes, moduleBytes);
6629
+ }
6630
+ } catch (err) {
6631
+ _iterator5.e(err);
6632
+ } finally {
6633
+ _iterator5.f();
6634
+ }
6635
+ return {
6636
+ bytes: bytes,
6637
+ versions: versions,
6638
+ modules: modules,
6639
+ mostVersionsOfOneModule: mostVersions,
6640
+ mostBytesOfOneModule: mostBytes,
6641
+ evicted: evicted
6642
+ };
6643
+ }
6644
+ };
6645
+ }
6646
+
6647
+ // #region bundle
6648
+
6649
+ /**
6650
+ * The build's own modules, free: answers only a key whose version is the
6651
+ * bundle's -- its schema, its base and the empty chain -- with the bundle's
6652
+ * own object, which the process holds for every visitor anyway. `bundle`
6653
+ * says what the build evaluated each module to; `null` for a module the
6654
+ * build does not have.
6655
+ */
6656
+ function bundleTier(bundle) {
6657
+ var name = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "bundle";
6658
+ return {
6659
+ name: name,
6660
+ get: function get(key) {
6661
+ return createForOfIteratorHelper._asyncToGenerator(/*#__PURE__*/createForOfIteratorHelper._regenerator().m(function _callee2() {
6662
+ var path, own, version;
6663
+ return createForOfIteratorHelper._regenerator().w(function (_context2) {
6664
+ while (1) switch (_context2.n) {
6665
+ case 0:
6666
+ path = pathOfModuleKey(key);
6667
+ if (!(path === null)) {
6668
+ _context2.n = 1;
6669
+ break;
6670
+ }
6671
+ return _context2.a(2, null);
6672
+ case 1:
6673
+ _context2.n = 2;
6674
+ return bundle(path);
6675
+ case 2:
6676
+ own = _context2.v;
6677
+ if (!(own === null)) {
6678
+ _context2.n = 3;
6679
+ break;
6680
+ }
6681
+ return _context2.a(2, null);
6682
+ case 3:
6683
+ version = createForOfIteratorHelper._objectSpread2(createForOfIteratorHelper._objectSpread2({}, own.version), {}, {
6684
+ chain: EMPTY_CHAIN
6685
+ });
6686
+ if (!(moduleKey(path, version) !== key)) {
6687
+ _context2.n = 4;
6688
+ break;
6689
+ }
6690
+ return _context2.a(2, null);
6691
+ case 4:
6692
+ return _context2.a(2, {
6693
+ path: path,
6694
+ version: version,
6695
+ content: baseModuleContent(own.source),
6696
+ patches: [],
6697
+ bytes: 0
6698
+ });
6699
+ }
6700
+ }, _callee2);
6701
+ }))();
6702
+ }
6703
+ };
6704
+ }
6705
+
6706
+ /** The module a {@link moduleKey} is the address of, or `null` for none. */
6707
+ function pathOfModuleKey(key) {
6708
+ var at = key.lastIndexOf("@");
6709
+ return at <= 0 ? null : key.slice(0, at);
6710
+ }
6711
+
6712
+ /**
6713
+ * The version a {@link moduleKey} names, or `null` when it is not one. The
6714
+ * three parts are hex or empty, so the split is unambiguous.
6715
+ */
6716
+ function parseModuleKey(key) {
6717
+ var path = pathOfModuleKey(key);
6718
+ if (path === null) return null;
6719
+ var parts = key.slice(path.length + 1).split(".");
6720
+ var _parts = createForOfIteratorHelper._slicedToArray(parts, 3),
6721
+ schema = _parts[0],
6722
+ base = _parts[1],
6723
+ chain = _parts[2];
6724
+ if (parts.length !== 3 || schema === undefined || base === undefined || chain === undefined) {
6725
+ return null;
6726
+ }
6727
+ return {
6728
+ path: path,
6729
+ version: {
6730
+ schema: schema,
6731
+ base: base,
6732
+ chain: chain
6733
+ }
6734
+ };
6735
+ }
6736
+
6737
+ // #region entries
6738
+
6739
+ /**
6740
+ * A `.jsonValues()` entry's version: the record's schema and base, the
6741
+ * entry's key, and the chain of the record's patches that touched THIS
6742
+ * entry -- so an entry no patch touched keeps its version while the record's
6743
+ * chain grows, and two reads at one entry version are the same content.
6744
+ * Entries are stored on their own, so a large record is not copied because
6745
+ * one entry changed.
6746
+ */
6747
+
6748
+ /** The address of an entry's content. Never changes meaning. */
6749
+ function entryAddress(path, v) {
6750
+ return "".concat(moduleKey(path, v), "#").concat(v.key);
6751
+ }
6752
+
6753
+ /** Whether two entry versions name the same content. */
6754
+ function sameEntryVersion(a, b) {
6755
+ return a.key === b.key && a.schema === b.schema && a.base === b.base && a.chain === b.chain;
6756
+ }
6757
+
6758
+ var SharedValConfig = zod.z.object({
6759
+ contentHostUrl: zod.z.string().optional(),
6760
+ appHostUrl: zod.z.string().optional(),
6761
+ project: zod.z.string().optional(),
6762
+ root: zod.z.string().optional(),
6763
+ gitCommit: zod.z.string().optional(),
6764
+ gitBranch: zod.z.string().optional(),
6765
+ defaultTheme: zod.z["enum"](["dark", "light"]).optional(),
6766
+ ai: zod.z.object({
6767
+ commitMessages: zod.z.object({
6768
+ disabled: zod.z["boolean"]().optional()
6769
+ }).optional()
6770
+ }).optional()
6771
+ });
6772
+
6773
+ /**
6774
+ * Config that is shared between the client and server.
4773
6775
  */
4774
6776
 
4775
6777
  /**
@@ -7969,7 +9971,9 @@ function createSearchIndex() {
7969
9971
  tokenize: "forward"
7970
9972
  }),
7971
9973
  pathToLabel: new Map(),
7972
- docsByModule: new Map()
9974
+ docsByModule: new Map(),
9975
+ walkedByModule: new Map(),
9976
+ entriesByModule: new Map()
7973
9977
  };
7974
9978
  }
7975
9979
 
@@ -7983,77 +9987,230 @@ function createSearchIndex() {
7983
9987
  * `.jsonValues()` entries are indexed only once their content is loaded — an
7984
9988
  * un-loaded entry is an opaque marker that {@link traverseSchemaSource} skips, so
7985
9989
  * the index is naturally PARTIAL and grows as batches land. That is why the search
7986
- * UI loads the entries on the first query and re-indexes (debounced) as they
7987
- * arrive, rather than pretending an empty result set is an answer.
9990
+ * UI loads the entries on the first query and re-indexes as they arrive, rather
9991
+ * than pretending an empty result set is an answer -- and why a module whose
9992
+ * root is a record is re-indexed by entry ({@link indexRecordModule}): a pass
9993
+ * per batch walks the batch, not the record.
7988
9994
  */
7989
- function indexModule(searchIndex, moduleFilePath, source, schema) {
7990
- var index = searchIndex.index,
7991
- pathToLabel = searchIndex.pathToLabel,
7992
- docsByModule = searchIndex.docsByModule;
9995
+ function indexModule(searchIndex, moduleFilePath, source, schema,
9996
+ /**
9997
+ * Stop by this time (`performance.now()`), for a caller that must not hold
9998
+ * the thread: answers `false` when it stopped with entries of a record
9999
+ * module still to walk, and the same call again goes on from there. A
10000
+ * module that is not a record is walked whole, whatever the time.
10001
+ */
10002
+ until) {
10003
+ var walked = searchIndex.walkedByModule.get(moduleFilePath);
10004
+ if (walked !== undefined && walked.source === source && walked.schema === schemaKeyOf(schema)) {
10005
+ return true;
10006
+ }
10007
+ if (schema.type === "record" && isJsonRecord(source)) {
10008
+ var done = indexRecordModule(searchIndex, moduleFilePath, source, schema, until);
10009
+ if (done) {
10010
+ searchIndex.walkedByModule.set(moduleFilePath, {
10011
+ source: source,
10012
+ schema: schemaKeyOf(schema)
10013
+ });
10014
+ }
10015
+ return done;
10016
+ }
7993
10017
  removeModule(searchIndex, moduleFilePath);
7994
10018
  var docs = new Set();
7995
- docsByModule.set(moduleFilePath, docs);
10019
+ searchIndex.docsByModule.set(moduleFilePath, docs);
7996
10020
  var path = moduleFilePath + "?p=";
7997
- traverseSchemaSource(source, schema, path, function (_ref) {
7998
- var source = _ref.source,
7999
- schema = _ref.schema,
8000
- path = _ref.path;
8001
- if (source === null) {
8002
- return;
8003
- }
8004
- var searchText = "";
8005
- var label = "";
8006
-
8007
- // Handle primitives
8008
- 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" ||
8009
- // An enum's value is a string the editor chose; it is as searchable as
8010
- // any other string leaf. The old string union was never indexed at all,
8011
- // which is the gap this closes.
8012
- schema.type === "enum" ||
8013
- // A locale tag, for the same reason: it is what the editor picked and
8014
- // what they see in the field, so searching "nb-NO" has to find the
8015
- // Norwegian rows. `traverseSchemaSource` visits these, and visiting a
8016
- // leaf only to index it as the empty string is worse than not visiting
8017
- // it — it looks like content that does not exist.
8018
- schema.type === "locale") {
8019
- var _source$toString, _source$toString2;
8020
- searchText = (_source$toString = source === null || source === void 0 ? void 0 : source.toString()) !== null && _source$toString !== void 0 ? _source$toString : "";
8021
- label = (_source$toString2 = source === null || source === void 0 ? void 0 : source.toString()) !== null && _source$toString2 !== void 0 ? _source$toString2 : "";
8022
- } else if (schema.type === "literal") {
8023
- searchText = schema.value.toString();
8024
- label = schema.value.toString();
8025
- }
8026
- // Handle richtext - flatten to get text content
8027
- else if (schema.type === "richtext") {
8028
- searchText = flattenRichText(source);
8029
- // Use first 50 chars as label
8030
- label = searchText.substring(0, 50) || "richtext";
8031
- }
8032
- // Handle file/image/video
8033
- else if (schema.type === "file" || schema.type === "image" || schema.type === "video") {
8034
- if (source !== null && createForOfIteratorHelper._typeof(source) === "object" && "path" in source && typeof source.path === "string") {
8035
- // The label is the bare filename - the folder is shown separately in
8036
- // the UI - but both are searchable.
8037
- var _getRefParts = getRefParts(source.path),
8038
- filename = _getRefParts.filename,
8039
- folder = _getRefParts.folder;
8040
- var alt = "alt" in source ? source.alt : "";
8041
- searchText = filename + " " + folder + " " + alt;
8042
- label = filename;
10021
+ traverseSchemaSource(source, schema, path, function (leaf) {
10022
+ var id = indexLeaf(searchIndex, leaf);
10023
+ if (id !== null) docs.add(id);
10024
+ });
10025
+ searchIndex.walkedByModule.set(moduleFilePath, {
10026
+ source: source,
10027
+ schema: schemaKeyOf(schema)
10028
+ });
10029
+ return true;
10030
+ }
10031
+
10032
+ /** How many entries are walked between two looks at the clock. */
10033
+ var ENTRIES_PER_CLOCK_CHECK = 32;
10034
+
10035
+ /**
10036
+ * A module whose root is a record, indexed entry by entry: an entry whose
10037
+ * value is the SAME OBJECT it was indexed from keeps its documents, and only
10038
+ * the others are walked.
10039
+ *
10040
+ * What makes loading a `.jsonValues()` record of 10,000 entries searchable
10041
+ * while it loads. The Studio asks again as each batch lands, and the record's
10042
+ * value is copy-on-write -- an entry nothing touched is the object it was --
10043
+ * so each pass indexes the batch that arrived, not the record. Walked whole,
10044
+ * that was the whole record per batch: quadratic, and a long task each time.
10045
+ *
10046
+ * Identity is only ever a shortcut: a value that is equal but a new object
10047
+ * (a structured clone across a worker, a module read again) is walked again,
10048
+ * never skipped. A different schema starts the module over.
10049
+ */
10050
+ function indexRecordModule(searchIndex, moduleFilePath, source, schema, until) {
10051
+ var index = searchIndex.index,
10052
+ pathToLabel = searchIndex.pathToLabel,
10053
+ docsByModule = searchIndex.docsByModule,
10054
+ entriesByModule = searchIndex.entriesByModule;
10055
+ var indexed = entriesByModule.get(moduleFilePath);
10056
+ var docs = docsByModule.get(moduleFilePath);
10057
+ if (indexed === undefined || docs === undefined || indexed.schema !== schema && schemaKeyOf(indexed.schema) !== schemaKeyOf(schema)) {
10058
+ removeModule(searchIndex, moduleFilePath);
10059
+ indexed = {
10060
+ schema: schema,
10061
+ entries: new Map()
10062
+ };
10063
+ docs = new Set();
10064
+ entriesByModule.set(moduleFilePath, indexed);
10065
+ docsByModule.set(moduleFilePath, docs);
10066
+ }
10067
+ var _indexed = indexed,
10068
+ entries = _indexed.entries;
10069
+ var _iterator = createForOfIteratorHelper._createForOfIteratorHelper(entries),
10070
+ _step;
10071
+ try {
10072
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
10073
+ var _step$value = createForOfIteratorHelper._slicedToArray(_step.value, 2),
10074
+ _key = _step$value[0],
10075
+ was = _step$value[1];
10076
+ if (Object.prototype.hasOwnProperty.call(source, _key) && source[_key] === was.source) {
10077
+ continue;
10078
+ }
10079
+ var _iterator2 = createForOfIteratorHelper._createForOfIteratorHelper(was.docs),
10080
+ _step2;
10081
+ try {
10082
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
10083
+ var _id = _step2.value;
10084
+ index.remove(_id);
10085
+ pathToLabel["delete"](_id);
10086
+ docs["delete"](_id);
10087
+ }
10088
+ } catch (err) {
10089
+ _iterator2.e(err);
10090
+ } finally {
10091
+ _iterator2.f();
8043
10092
  }
10093
+ entries["delete"](_key);
8044
10094
  }
10095
+ } catch (err) {
10096
+ _iterator.e(err);
10097
+ } finally {
10098
+ _iterator.f();
10099
+ }
10100
+ var path = moduleFilePath + "?p=";
10101
+ var walked = 0;
10102
+ var _loop = function _loop() {
10103
+ if (entries.has(key)) return 0; // continue
10104
+ if (until !== undefined && ++walked % ENTRIES_PER_CLOCK_CHECK === 0 && performance.now() > until) {
10105
+ return {
10106
+ v: false
10107
+ };
10108
+ }
10109
+ var value = source[key];
10110
+ var entryDocs = [];
10111
+ traverseSchemaSource(value, schema.item, sourcePathOfChild(path, key), function (leaf) {
10112
+ var id = indexLeaf(searchIndex, leaf);
10113
+ if (id === null) return;
10114
+ entryDocs.push(id);
10115
+ docs.add(id);
10116
+ });
10117
+ entries.set(key, {
10118
+ source: value,
10119
+ docs: entryDocs
10120
+ });
10121
+ },
10122
+ _ret;
10123
+ for (var key in source) {
10124
+ _ret = _loop();
10125
+ if (_ret === 0) continue;
10126
+ if (_ret) return _ret.v;
10127
+ }
10128
+ return true;
10129
+ }
10130
+ function isJsonRecord(source) {
10131
+ return createForOfIteratorHelper._typeof(source) === "object" && source !== null && !Array.isArray(source);
10132
+ }
10133
+
10134
+ /**
10135
+ * A schema by its content, kept per schema object. Its object is not enough:
10136
+ * the Studio receives a module's schema again (new code, a re-read of the
10137
+ * project) as a new object that says the same, and starting a record of
10138
+ * 10,000 entries over for that was seconds of removing and adding
10139
+ * documents in one go.
10140
+ */
10141
+ var schemaKeys = new WeakMap();
10142
+ function schemaKeyOf(schema) {
10143
+ var key = schemaKeys.get(schema);
10144
+ if (key === undefined) {
10145
+ key = JSON.stringify(schema);
10146
+ schemaKeys.set(schema, key);
10147
+ }
10148
+ return key;
10149
+ }
10150
+
10151
+ /** One leaf as a document, if it has anything to find. Its id, or `null`. */
10152
+ function indexLeaf(searchIndex, _ref) {
10153
+ var source = _ref.source,
10154
+ schema = _ref.schema,
10155
+ path = _ref.path;
10156
+ if (source === null) {
10157
+ return null;
10158
+ }
10159
+ var index = searchIndex.index,
10160
+ pathToLabel = searchIndex.pathToLabel;
10161
+ var searchText = "";
10162
+ var label = "";
8045
10163
 
8046
- // Add to index if we have search text
8047
- if (searchText) {
8048
- var _Internal$splitModule = core.Internal.splitModuleFilePathAndModulePath(path),
8049
- _Internal$splitModule2 = createForOfIteratorHelper._slicedToArray(_Internal$splitModule, 2),
8050
- modulePath = _Internal$splitModule2[1];
8051
- var cleanPath = fastRemoveNonWordChars(modulePath) + " ";
8052
- index.add(path, cleanPath + " " + path + " " + searchText);
8053
- pathToLabel.set(path, label);
8054
- docs.add(path);
10164
+ // Handle primitives
10165
+ 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" ||
10166
+ // An enum's value is a string the editor chose; it is as searchable as
10167
+ // any other string leaf. The old string union was never indexed at all,
10168
+ // which is the gap this closes.
10169
+ schema.type === "enum" ||
10170
+ // A locale tag, for the same reason: it is what the editor picked and
10171
+ // what they see in the field, so searching "nb-NO" has to find the
10172
+ // Norwegian rows. `traverseSchemaSource` visits these, and visiting a
10173
+ // leaf only to index it as the empty string is worse than not visiting
10174
+ // it — it looks like content that does not exist.
10175
+ schema.type === "locale") {
10176
+ var _source$toString, _source$toString2;
10177
+ searchText = (_source$toString = source === null || source === void 0 ? void 0 : source.toString()) !== null && _source$toString !== void 0 ? _source$toString : "";
10178
+ label = (_source$toString2 = source === null || source === void 0 ? void 0 : source.toString()) !== null && _source$toString2 !== void 0 ? _source$toString2 : "";
10179
+ } else if (schema.type === "literal") {
10180
+ searchText = schema.value.toString();
10181
+ label = schema.value.toString();
10182
+ }
10183
+ // Handle richtext - flatten to get text content
10184
+ else if (schema.type === "richtext") {
10185
+ searchText = flattenRichText(source);
10186
+ // Use first 50 chars as label
10187
+ label = searchText.substring(0, 50) || "richtext";
10188
+ }
10189
+ // Handle file/image/video
10190
+ else if (schema.type === "file" || schema.type === "image" || schema.type === "video") {
10191
+ if (source !== null && createForOfIteratorHelper._typeof(source) === "object" && "path" in source && typeof source.path === "string") {
10192
+ // The label is the bare filename - the folder is shown separately in
10193
+ // the UI - but both are searchable.
10194
+ var _getRefParts = getRefParts(source.path),
10195
+ filename = _getRefParts.filename,
10196
+ folder = _getRefParts.folder;
10197
+ var alt = "alt" in source ? source.alt : "";
10198
+ searchText = filename + " " + folder + " " + alt;
10199
+ label = filename;
8055
10200
  }
8056
- });
10201
+ }
10202
+
10203
+ // Add to index if we have search text
10204
+ if (!searchText) {
10205
+ return null;
10206
+ }
10207
+ var _Internal$splitModule = core.Internal.splitModuleFilePathAndModulePath(path),
10208
+ _Internal$splitModule2 = createForOfIteratorHelper._slicedToArray(_Internal$splitModule, 2),
10209
+ modulePath = _Internal$splitModule2[1];
10210
+ var cleanPath = fastRemoveNonWordChars(modulePath) + " ";
10211
+ index.add(path, cleanPath + " " + path + " " + searchText);
10212
+ pathToLabel.set(path, label);
10213
+ return path;
8057
10214
  }
8058
10215
 
8059
10216
  /**
@@ -8065,21 +10222,24 @@ function indexModule(searchIndex, moduleFilePath, source, schema) {
8065
10222
  function removeModule(searchIndex, moduleFilePath) {
8066
10223
  var index = searchIndex.index,
8067
10224
  pathToLabel = searchIndex.pathToLabel,
8068
- docsByModule = searchIndex.docsByModule;
10225
+ docsByModule = searchIndex.docsByModule,
10226
+ entriesByModule = searchIndex.entriesByModule;
10227
+ entriesByModule["delete"](moduleFilePath);
10228
+ searchIndex.walkedByModule["delete"](moduleFilePath);
8069
10229
  var existing = docsByModule.get(moduleFilePath);
8070
10230
  if (existing === undefined) return;
8071
- var _iterator = createForOfIteratorHelper._createForOfIteratorHelper(existing),
8072
- _step;
10231
+ var _iterator3 = createForOfIteratorHelper._createForOfIteratorHelper(existing),
10232
+ _step3;
8073
10233
  try {
8074
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
8075
- var _id = _step.value;
8076
- index.remove(_id);
8077
- pathToLabel["delete"](_id);
10234
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
10235
+ var _id2 = _step3.value;
10236
+ index.remove(_id2);
10237
+ pathToLabel["delete"](_id2);
8078
10238
  }
8079
10239
  } catch (err) {
8080
- _iterator.e(err);
10240
+ _iterator3.e(err);
8081
10241
  } finally {
8082
- _iterator.f();
10242
+ _iterator3.f();
8083
10243
  }
8084
10244
  docsByModule["delete"](moduleFilePath);
8085
10245
  }
@@ -9554,27 +11714,30 @@ var messageOf = function messageOf(error) {
9554
11714
  return error instanceof Error ? error.message : String(error);
9555
11715
  };
9556
11716
 
9557
- exports.PublishProtocolError = protocol.PublishProtocolError;
9558
- exports.parseArtifacts = protocol.parseArtifacts;
9559
- exports.parseBuildTarget = protocol.parseBuildTarget;
9560
- exports.parseCancel = protocol.parseCancel;
9561
- exports.parseDeclare = protocol.parseDeclare;
9562
- exports.parseDiscard = protocol.parseDiscard;
9563
- exports.parseJob = protocol.parseJob;
9564
- exports.parseNewestCiRun = protocol.parseNewestCiRun;
9565
- exports.parsePress = protocol.parsePress;
9566
- exports.parseProblems = protocol.parseProblems;
9567
- exports.parseProjectSource = protocol.parseProjectSource;
9568
- exports.parsePromote = protocol.parsePromote;
9569
- exports.parsePublishToken = protocol.parsePublishToken;
9570
- exports.parseRenew = protocol.parseRenew;
9571
- exports.parseRequestStatus = protocol.parseRequestStatus;
9572
- exports.parseStatus = protocol.parseStatus;
9573
- exports.parseUpdateTarget = protocol.parseUpdateTarget;
9574
- exports.parseVerify = protocol.parseVerify;
11717
+ exports.PublishProtocolError = projectSource.PublishProtocolError;
11718
+ exports.createProjectSourceReader = projectSource.createProjectSourceReader;
11719
+ exports.parseArtifacts = projectSource.parseArtifacts;
11720
+ exports.parseBuildTarget = projectSource.parseBuildTarget;
11721
+ exports.parseCancel = projectSource.parseCancel;
11722
+ exports.parseDeclare = projectSource.parseDeclare;
11723
+ exports.parseDiscard = projectSource.parseDiscard;
11724
+ exports.parseJob = projectSource.parseJob;
11725
+ exports.parseNewestCiRun = projectSource.parseNewestCiRun;
11726
+ exports.parsePress = projectSource.parsePress;
11727
+ exports.parseProblems = projectSource.parseProblems;
11728
+ exports.parseProjectSource = projectSource.parseProjectSource;
11729
+ exports.parsePromote = projectSource.parsePromote;
11730
+ exports.parsePublishToken = projectSource.parsePublishToken;
11731
+ exports.parseRenew = projectSource.parseRenew;
11732
+ exports.parseRequestStatus = projectSource.parseRequestStatus;
11733
+ exports.parseStatus = projectSource.parseStatus;
11734
+ exports.parseUpdateTarget = projectSource.parseUpdateTarget;
11735
+ exports.parseVerify = projectSource.parseVerify;
11736
+ exports.publishApiOf = projectSource.publishApiOf;
9575
11737
  exports.DEFAULT_CANVAS_SELECTION = getSitemapTree.DEFAULT_CANVAS_SELECTION;
9576
11738
  exports.DEFAULT_CANVAS_SELECTION_SOFT = getSitemapTree.DEFAULT_CANVAS_SELECTION_SOFT;
9577
11739
  exports.QUICK_PREVIEW_PARAM = getSitemapTree.QUICK_PREVIEW_PARAM;
11740
+ exports.QUICK_PREVIEW_RENDER_SCRIPT = getSitemapTree.QUICK_PREVIEW_RENDER_SCRIPT;
9578
11741
  exports.QUICK_PREVIEW_SERVICE_WORKER_PATH = getSitemapTree.QUICK_PREVIEW_SERVICE_WORKER_PATH;
9579
11742
  exports.QUICK_PREVIEW_SERVICE_WORKER_SOURCE = getSitemapTree.QUICK_PREVIEW_SERVICE_WORKER_SOURCE;
9580
11743
  exports.QUICK_PREVIEW_STORE_MESSAGE = getSitemapTree.QUICK_PREVIEW_STORE_MESSAGE;
@@ -9619,12 +11782,15 @@ exports.AnalyticsBeacon = AnalyticsBeacon;
9619
11782
  exports.Api = Api;
9620
11783
  exports.BRAND_CONTRAST_PAIRS = BRAND_CONTRAST_PAIRS;
9621
11784
  exports.BinaryFileRef = BinaryFileRef;
11785
+ exports.ChainFold = ChainFold;
9622
11786
  exports.CodeOptions = CodeOptions;
9623
11787
  exports.ColorOptions = ColorOptions;
9624
11788
  exports.DEFAULT_ERROR_MESSAGE = DEFAULT_ERROR_MESSAGE;
9625
11789
  exports.DateOptions = DateOptions;
11790
+ exports.EMPTY_CHAIN = EMPTY_CHAIN;
9626
11791
  exports.ENCODE_DEFAULTS = ENCODE_DEFAULTS;
9627
11792
  exports.ENCODE_MIME_TYPE_OF = ENCODE_MIME_TYPE_OF;
11793
+ exports.ENGINE = ENGINE;
9628
11794
  exports.FileOptions = FileOptions;
9629
11795
  exports.HistoricalCommit = HistoricalCommit;
9630
11796
  exports.HistoricalModule = HistoricalModule;
@@ -9637,6 +11803,7 @@ exports.JSON_ENTRIES_BATCH_MAX = JSON_ENTRIES_BATCH_MAX;
9637
11803
  exports.LOCAL_FILE_IN_REMOTE_PROJECT_HINT = LOCAL_FILE_IN_REMOTE_PROJECT_HINT;
9638
11804
  exports.LOCAL_FILE_IN_REMOTE_PROJECT_WARNING = LOCAL_FILE_IN_REMOTE_PROJECT_WARNING;
9639
11805
  exports.ModuleComparison = ModuleComparison;
11806
+ exports.ModuleVersionSchema = ModuleVersionSchema;
9640
11807
  exports.NEVER_ENCODED = NEVER_ENCODED;
9641
11808
  exports.ParentRef = ParentRef$1;
9642
11809
  exports.Patch = Patch;
@@ -9645,6 +11812,7 @@ exports.PatchId = PatchId;
9645
11812
  exports.PatchJSON = PatchJSON;
9646
11813
  exports.PatchSets = PatchSets;
9647
11814
  exports.PlanRefusalError = PlanRefusalError;
11815
+ exports.ProjectVersionSchema = ProjectVersionSchema;
9648
11816
  exports.RAMP_STEPS = RAMP_STEPS;
9649
11817
  exports.RemirrorBr = RemirrorBr;
9650
11818
  exports.RemirrorBulletList = RemirrorBulletList;
@@ -9662,6 +11830,7 @@ exports.RichTextOptions = RichTextOptions;
9662
11830
  exports.SCHEMA_SOURCE_FIXES = SCHEMA_SOURCE_FIXES;
9663
11831
  exports.SELF_CHECK_VERSION = SELF_CHECK_VERSION;
9664
11832
  exports.SKETCHPAD_MAX_TEXT_LENGTH = SKETCHPAD_MAX_TEXT_LENGTH;
11833
+ exports.STAGED_SCHEMA_PREFIX = STAGED_SCHEMA_PREFIX;
9665
11834
  exports.STUDIO_FIXES = STUDIO_FIXES;
9666
11835
  exports.SerializedArraySchema = SerializedArraySchema;
9667
11836
  exports.SerializedBooleanSchema = SerializedBooleanSchema;
@@ -9689,6 +11858,7 @@ exports.SerializedVideoSchema = SerializedVideoSchema;
9689
11858
  exports.SharedValConfig = SharedValConfig;
9690
11859
  exports.Sketchpad = Sketchpad;
9691
11860
  exports.SketchpadSummary = SketchpadSummary;
11861
+ exports.VAL_CLIENT_HEADER = VAL_CLIENT_HEADER;
9692
11862
  exports.VAL_ENABLE_COOKIE_NAME = VAL_ENABLE_COOKIE_NAME;
9693
11863
  exports.VAL_GREEN_RAMP = VAL_GREEN_RAMP;
9694
11864
  exports.VAL_SESSION_COOKIE = VAL_SESSION_COOKIE;
@@ -9701,17 +11871,24 @@ exports.VideoStreamOption = VideoStreamOption;
9701
11871
  exports.WARNING_FIXES = WARNING_FIXES;
9702
11872
  exports.WASM_ENCODED_MIME_TYPES = WASM_ENCODED_MIME_TYPES;
9703
11873
  exports.accentRamp = accentRamp;
11874
+ exports.applyChain = applyChain;
11875
+ exports.applyPatchRecord = applyPatchRecord;
11876
+ exports.baseModuleContent = baseModuleContent;
11877
+ exports.baseVersionOf = baseVersionOf;
9704
11878
  exports.buildDuplicatePatch = buildDuplicatePatch;
9705
11879
  exports.buildEmptyAtPathPatch = buildEmptyAtPathPatch;
9706
11880
  exports.buildFileRefValue = buildFileRefValue;
9707
11881
  exports.buildImageGalleryPatch = buildImageGalleryPatch;
9708
11882
  exports.buildRemoveImageGalleryEntryPatch = buildRemoveImageGalleryEntryPatch;
9709
11883
  exports.buildSearchIndex = buildSearchIndex;
11884
+ exports.bundleTier = bundleTier;
9710
11885
  exports.chainHeadOf = chainHeadOf;
11886
+ exports.chainOf = chainOf;
9711
11887
  exports.checkCompatibility = checkCompatibility;
9712
11888
  exports.checkSketch = checkSketch;
9713
11889
  exports.childSource = childSource;
9714
11890
  exports.chooseEncoded = chooseEncoded;
11891
+ exports.classifyJsonValuesOp = classifyJsonValuesOp;
9715
11892
  exports.concatModulePath = concatModulePath;
9716
11893
  exports.contrastRatio = contrastRatio;
9717
11894
  exports.createRegExpFromPattern = createRegExpFromPattern;
@@ -9720,8 +11897,14 @@ exports.createValClient = createValClient;
9720
11897
  exports.declaredLocales = declaredLocales;
9721
11898
  exports.describeContainerAtPath = describeContainerAtPath;
9722
11899
  exports.emptyOf = emptyOf;
11900
+ exports.engineWork = engineWork;
11901
+ exports.entryAddress = entryAddress;
11902
+ exports.entryState = entryState;
9723
11903
  exports.enumLocaleTag = enumLocaleTag;
11904
+ exports.expandJsonValuesRootOp = expandJsonValuesRootOp;
11905
+ exports.expandJsonValuesRootOpForKey = expandJsonValuesRootOpForKey;
9724
11906
  exports.explainIncompatible = explainIncompatible;
11907
+ exports.extendChain = extendChain;
9725
11908
  exports.fallbackMimeType = fallbackMimeType;
9726
11909
  exports.filterBlockingValidationErrors = filterBlockingValidationErrors;
9727
11910
  exports.filterRoutesByPatterns = filterRoutesByPatterns;
@@ -9745,6 +11928,7 @@ exports.hasLocaleRouteParam = hasLocaleRouteParam;
9745
11928
  exports.indexModule = indexModule;
9746
11929
  exports.isInsidePatchSetPath = isInsidePatchSetPath;
9747
11930
  exports.isJsonObject = isJsonObject;
11931
+ exports.isJsonValuesRootOp = isJsonValuesRootOp;
9748
11932
  exports.isLocaleSchema = isLocaleSchema;
9749
11933
  exports.isMimeTypeAccepted = isMimeTypeAccepted;
9750
11934
  exports.isRemoteSchema = isRemoteSchema;
@@ -9763,17 +11947,22 @@ exports.localeOfModuleRoot = localeOfModuleRoot;
9763
11947
  exports.localeOfRouteKey = localeOfRouteKey;
9764
11948
  exports.localeParamOfRouteKey = localeParamOfRouteKey;
9765
11949
  exports.localeScopeAt = localeScopeAt;
9766
- exports.memoizePerRequest = memoizePerRequest;
11950
+ exports.memoryTier = memoryTier;
11951
+ exports.moduleKey = moduleKey;
9767
11952
  exports.newestCommitSha = newestCommitSha;
11953
+ exports.parseModuleKey = parseModuleKey;
9768
11954
  exports.parseSketch = parseSketch;
9769
11955
  exports.partitionValidationErrors = partitionValidationErrors;
11956
+ exports.pathOfModuleKey = pathOfModuleKey;
9770
11957
  exports.performSearch = performSearch;
9771
11958
  exports.proposalCustomProperties = proposalCustomProperties;
11959
+ exports.rebaseContentOp = rebaseContentOp;
9772
11960
  exports.relativeLuminance = relativeLuminance;
9773
11961
  exports.relocateForLocale = relocateForLocale;
9774
11962
  exports.remirrorToRichTextSource = remirrorToRichTextSource;
9775
11963
  exports.removeModule = removeModule;
9776
11964
  exports.resolveEncodeSettings = resolveEncodeSettings;
11965
+ exports.resolveEntry = resolveEntry;
9777
11966
  exports.resolvePairGround = resolvePairGround;
9778
11967
  exports.resolveSchemaSourceFixForError = resolveSchemaSourceFixForError;
9779
11968
  exports.resolveSchemaSourceFixes = resolveSchemaSourceFixes;
@@ -9781,8 +11970,11 @@ exports.resolveSerializedSchemaAtPath = resolveSerializedSchemaAtPath;
9781
11970
  exports.richTextImageSchema = richTextImageSchema;
9782
11971
  exports.richTextToRemirror = richTextToRemirror;
9783
11972
  exports.safeParsePatch = safeParsePatch;
11973
+ exports.sameEntryVersion = sameEntryVersion;
11974
+ exports.sameModuleVersion = sameModuleVersion;
9784
11975
  exports.schemaErrorsOf = schemaErrorsOf;
9785
11976
  exports.schemaTypesOfPath = schemaTypesOfPath;
11977
+ exports.schemaVersionOf = schemaVersionOf;
9786
11978
  exports.selectionClosure = selectionClosure;
9787
11979
  exports.selfCheck = selfCheck;
9788
11980
  exports.shouldEncodeWithWasm = shouldEncodeWithWasm;
@@ -9790,9 +11982,16 @@ exports.sideFiles = sideFiles;
9790
11982
  exports.sideSource = sideSource;
9791
11983
  exports.sketchTitle = sketchTitle;
9792
11984
  exports.sourcePathOfChild = sourcePathOfChild;
11985
+ exports.storeWork = storeWork;
9793
11986
  exports.themeCustomProperties = themeCustomProperties;
11987
+ exports.tiered = tiered;
9794
11988
  exports.toolNames = toolNames;
9795
11989
  exports.traverseSchemaSource = traverseSchemaSource;
9796
11990
  exports.urlOf = urlOf;
11991
+ exports.valClientHeaders = valClientHeaders;
11992
+ exports.valModulesEntryFile = valModulesEntryFile;
11993
+ exports.valModulesEntryPath = valModulesEntryPath;
9797
11994
  exports.validateRoutePatterns = validateRoutePatterns;
11995
+ exports.versionHashCount = versionHashCount;
11996
+ exports.wholeEntryKey = wholeEntryKey;
9798
11997
  exports.withExtension = withExtension;