@valbuild/shared 0.121.0 → 0.122.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.
@@ -901,6 +901,12 @@ function _asyncToGenerator(n) {
901
901
  };
902
902
  }
903
903
 
904
+ /**
905
+ * JSON, recursively — the shape of a Source over the wire.
906
+ *
907
+ * Exported because history reads stored Sources back and needs the same
908
+ * definition; a second copy is a second thing to keep in step.
909
+ */
904
910
  var JSONValue = zod.z.lazy(function () {
905
911
  return zod.z.union([zod.z.string(), zod.z.number(), zod.z["boolean"](), zod.z["null"](), zod.z.array(JSONValue), zod.z.record(zod.z.string(), JSONValue)]);
906
912
  });
@@ -1279,7 +1285,26 @@ var SerializedRouteSchema = zod.z.object({
1279
1285
  readonly: zod.z["boolean"]().optional(),
1280
1286
  hidden: zod.z["boolean"]().optional()
1281
1287
  });
1282
- var SerializedSchema = zod.z.union([SerializedStringSchema, SerializedLiteralSchema, SerializedBooleanSchema, SerializedNumberSchema, SerializedObjectSchema, SerializedArraySchema, SerializedUnionSchema, SerializedRichTextSchema, SerializedRecordSchema, SerializedKeyOfSchema, SerializedRouteSchema, SerializedFileSchema, SerializedDateSchema, SerializedDateTimeSchema, SerializedColorSchema, SerializedCodeSchema, SerializedImageSchema]);
1288
+
1289
+ // A settings module, and each section inside one, serialize as this - the
1290
+ // shape is recursive because `items` holds sections which are themselves
1291
+ // settings schemas. `s.settings()` never writes render/preview/customValidate
1292
+ // (see core/src/schema/settings.ts), but they are accepted here because they
1293
+ // are part of the declared type.
1294
+ var SerializedSettingsSchema = zod.z.lazy(function () {
1295
+ return zod.z.object({
1296
+ type: zod.z.literal("settings"),
1297
+ render: FieldRender,
1298
+ preview: zod.z.literal(true).optional(),
1299
+ items: zod.z.record(zod.z.string(), SerializedSchema),
1300
+ opt: zod.z["boolean"](),
1301
+ customValidate: zod.z["boolean"]().optional(),
1302
+ readonly: zod.z["boolean"]().optional(),
1303
+ hidden: zod.z["boolean"]().optional(),
1304
+ description: zod.z.string().optional()
1305
+ });
1306
+ });
1307
+ var SerializedSchema = zod.z.union([SerializedStringSchema, SerializedLiteralSchema, SerializedBooleanSchema, SerializedNumberSchema, SerializedObjectSchema, SerializedArraySchema, SerializedUnionSchema, SerializedRichTextSchema, SerializedRecordSchema, SerializedKeyOfSchema, SerializedRouteSchema, SerializedFileSchema, SerializedDateSchema, SerializedDateTimeSchema, SerializedColorSchema, SerializedCodeSchema, SerializedSettingsSchema, SerializedImageSchema]);
1283
1308
 
1284
1309
  var ValCommit = zod.z.object({
1285
1310
  commitSha: zod.z.string(),
@@ -1291,6 +1316,125 @@ var ValCommit = zod.z.object({
1291
1316
  commitMessage: zod.z.string().nullable()
1292
1317
  });
1293
1318
 
1319
+ /**
1320
+ * The wire shapes for reading history.
1321
+ *
1322
+ * Here rather than in `@valbuild/server` because both sides of `/api/val` need
1323
+ * them: the server builds these and the Studio parses them, and a second
1324
+ * hand-written copy in the client is how the two drift.
1325
+ *
1326
+ * `HistoryError` is a closed union of everything that can go wrong
1327
+ * reconstructing a commit - see `packages/server/src/history/HistoryError.ts`
1328
+ * for what each one means. It travels as data rather than as a message because
1329
+ * the Studio decides what to OFFER from it: a schema mismatch means "cannot
1330
+ * restore this field", a missing source means "cannot restore this module at
1331
+ * all", and a rendered string cannot be told apart.
1332
+ */
1333
+ var HistoryError = zod.z.discriminatedUnion("kind", [zod.z.object({
1334
+ kind: zod.z.literal("commit-not-found"),
1335
+ commitSha: zod.z.string()
1336
+ }), zod.z.object({
1337
+ kind: zod.z.literal("archive-unreadable"),
1338
+ commitSha: zod.z.string(),
1339
+ message: zod.z.string()
1340
+ }), zod.z.object({
1341
+ kind: zod.z.literal("source-unavailable"),
1342
+ moduleFilePath: zod.z.string()
1343
+ }), zod.z.object({
1344
+ kind: zod.z.literal("schema-unreadable"),
1345
+ moduleFilePath: zod.z.string(),
1346
+ message: zod.z.string()
1347
+ }), zod.z.object({
1348
+ kind: zod.z.literal("file-unavailable"),
1349
+ gitPath: zod.z.string(),
1350
+ message: zod.z.string()
1351
+ }), zod.z.object({
1352
+ kind: zod.z.literal("not-supported-in-fs-mode")
1353
+ }), zod.z.object({
1354
+ kind: zod.z.literal("transport"),
1355
+ message: zod.z.string()
1356
+ })]);
1357
+ var HistoricalCommit = zod.z.object({
1358
+ commitSha: zod.z.string(),
1359
+ parentCommitSha: zod.z.string(),
1360
+ clientCommitSha: zod.z.string(),
1361
+ branch: zod.z.string(),
1362
+ createdBranch: zod.z.string().nullable(),
1363
+ creator: zod.z.string().nullable(),
1364
+ message: zod.z.string().nullable(),
1365
+ createdAt: zod.z.string(),
1366
+ seqNum: zod.z.string(),
1367
+ patchCount: zod.z.number(),
1368
+ /** False for commits made before history was recorded. */
1369
+ hasArchive: zod.z["boolean"]()
1370
+ });
1371
+ var FileChange = zod.z.union([zod.z.literal("added"), zod.z.literal("modified"), zod.z.literal("deleted")]);
1372
+
1373
+ /**
1374
+ * A binary file the commit touched - named, with a URL, and nothing fetched.
1375
+ * Only an `<img src>` that mounts pays for the bytes.
1376
+ */
1377
+ var BinaryFileRef = zod.z.object({
1378
+ gitPath: zod.z.string(),
1379
+ change: FileChange,
1380
+ remote: zod.z["boolean"](),
1381
+ url: zod.z.string()
1382
+ });
1383
+ var HistoricalModule = zod.z.object({
1384
+ /** The module's data at this commit. Null if deleted, or unreadable. */
1385
+ source: JSONValue.nullable(),
1386
+ /**
1387
+ * The schema at this commit, already validated against THIS Val.
1388
+ *
1389
+ * Null with a `schema-unreadable` failure means the stored schema came from a
1390
+ * Val whose schema format this one does not know — expected, not damage.
1391
+ */
1392
+ schema: SerializedSchema.nullable(),
1393
+ patchIds: zod.z.array(PatchId),
1394
+ changedPaths: zod.z.array(zod.z.string()),
1395
+ failures: zod.z.array(HistoryError)
1396
+ });
1397
+ var HistoricalPatchSet = zod.z.object({
1398
+ commit: HistoricalCommit,
1399
+ modules: zod.z.record(zod.z.string(), HistoricalModule),
1400
+ patches: zod.z.array(zod.z.object({
1401
+ patchId: PatchId,
1402
+ moduleFilePath: zod.z.string(),
1403
+ patch: zod.z.unknown(),
1404
+ authorId: zod.z.string().nullable(),
1405
+ createdAt: zod.z.string(),
1406
+ baseSha: zod.z.string(),
1407
+ coreVersion: zod.z.string()
1408
+ })),
1409
+ jsonEntries: zod.z.record(zod.z.string(), zod.z.unknown()),
1410
+ binaryFiles: zod.z.array(BinaryFileRef),
1411
+ warnings: zod.z.array(HistoryError)
1412
+ });
1413
+ /**
1414
+ * Whether the historical value can be written back as the project stands.
1415
+ *
1416
+ * Computed in the Studio, not on the server: the verdict depends on the current
1417
+ * source and the current schema, both of which the client already holds, and it
1418
+ * runs against the same worker-thread validator that validates ordinary edits.
1419
+ * The types stay here because they are what a restore preview hands to the UI.
1420
+ *
1421
+ * `blocked` carries its reasons so a refusal can name the field and say why,
1422
+ * rather than being an unexplained disabled button.
1423
+ */
1424
+ var RestoreVerdict = zod.z.union([zod.z.object({
1425
+ status: zod.z.literal("restorable")
1426
+ }), zod.z.object({
1427
+ status: zod.z.literal("blocked"),
1428
+ reasons: zod.z.array(HistoryError)
1429
+ })]);
1430
+ var ModuleComparison = zod.z.object({
1431
+ current: zod.z.unknown().nullable(),
1432
+ /** Paths where the commit's result and the current source differ: what a
1433
+ * restore would undo. */
1434
+ changedVsCurrent: zod.z.array(zod.z.string()),
1435
+ verdict: RestoreVerdict
1436
+ });
1437
+
1294
1438
  var ModuleFilePath = zod.z.string().refine(function (_path) {
1295
1439
  return true;
1296
1440
  } // TODO: validation
@@ -1560,48 +1704,6 @@ var Api = {
1560
1704
  })
1561
1705
  }
1562
1706
  },
1563
- "/session": {
1564
- GET: {
1565
- req: {
1566
- cookies: {
1567
- val_session: zod.z.string().optional()
1568
- }
1569
- },
1570
- res: zod.z.union([zod.z.object({
1571
- status: zod.z.literal(200),
1572
- json: zod.z.object({
1573
- mode: zod.z.union([zod.z.literal("local"), zod.z.literal("proxy")]),
1574
- enabled: zod.z["boolean"]()
1575
- })
1576
- }), zod.z.object({
1577
- status: zod.z.union([
1578
- // TODO: Remove the ones we don't need.
1579
- zod.z.literal(400), zod.z.literal(401), zod.z.literal(403), zod.z.literal(404), zod.z.literal(500), zod.z.literal(501)]),
1580
- json: zod.z.object({
1581
- message: zod.z.string()
1582
- })
1583
- }), zod.z.object({
1584
- status: zod.z.literal(401),
1585
- json: zod.z.object({
1586
- message: zod.z.string(),
1587
- details: zod.z.union([zod.z.string(), zod.z.object({
1588
- reason: zod.z.string()
1589
- }), zod.z.object({
1590
- sub: zod.z.string(),
1591
- exp: zod.z.number(),
1592
- token: zod.z.string(),
1593
- org: zod.z.string(),
1594
- project: zod.z.string()
1595
- })])
1596
- })
1597
- }), zod.z.object({
1598
- status: zod.z.literal(500),
1599
- json: zod.z.object({
1600
- message: zod.z.string()
1601
- })
1602
- })])
1603
- }
1604
- },
1605
1707
  "/logout": {
1606
1708
  GET: {
1607
1709
  req: {
@@ -2393,6 +2495,144 @@ var Api = {
2393
2495
  })])
2394
2496
  }
2395
2497
  },
2498
+ // #region history
2499
+ //
2500
+ // Reading the past. Split across three routes on purpose, because they cache
2501
+ // differently: a reconstructed commit cannot change and is immutable
2502
+ // forever, the comparison against current source changes with every edit, and
2503
+ // a file at a fixed commit is immutable too. Folding them together would mean
2504
+ // the cheapest thing dragging the most expensive one's cache headers down to
2505
+ // no-store.
2506
+ "/history/commits": {
2507
+ GET: {
2508
+ req: {
2509
+ query: {
2510
+ branch: onlyOneStringQueryParam,
2511
+ limit: onlyOneIntQueryParam.optional(),
2512
+ /** The previous page's `nextCursor`. */
2513
+ cursor: onlyOneStringQueryParam.optional()
2514
+ },
2515
+ cookies: {
2516
+ val_session: zod.z.string().optional()
2517
+ }
2518
+ },
2519
+ res: zod.z.union([unauthorizedResponse, notFoundResponse, zod.z.object({
2520
+ status: zod.z.literal(400),
2521
+ json: GenericError.and(zod.z.object({
2522
+ kind: zod.z.string().optional()
2523
+ }))
2524
+ }), zod.z.object({
2525
+ status: zod.z.literal(500),
2526
+ json: GenericError.and(zod.z.object({
2527
+ kind: zod.z.string().optional()
2528
+ }))
2529
+ }), zod.z.object({
2530
+ status: zod.z.literal(200),
2531
+ json: zod.z.object({
2532
+ commits: zod.z.array(HistoricalCommit),
2533
+ nextCursor: zod.z.string().nullable()
2534
+ })
2535
+ })])
2536
+ }
2537
+ },
2538
+ /**
2539
+ * One commit, reconstructed. IMMUTABLE for a given sha - it says nothing
2540
+ * about the current source or schema, which is what lets it be cached
2541
+ * forever and what makes comparing many commits cheap.
2542
+ */
2543
+ "/history/commit": {
2544
+ GET: {
2545
+ req: {
2546
+ query: {
2547
+ commit_sha: onlyOneStringQueryParam
2548
+ },
2549
+ cookies: {
2550
+ val_session: zod.z.string().optional()
2551
+ }
2552
+ },
2553
+ res: zod.z.union([unauthorizedResponse, notFoundResponse, zod.z.object({
2554
+ status: zod.z.literal(400),
2555
+ json: GenericError
2556
+ }), zod.z.object({
2557
+ status: zod.z.literal(500),
2558
+ json: GenericError.and(zod.z.object({
2559
+ kind: zod.z.string().optional()
2560
+ }))
2561
+ }), zod.z.object({
2562
+ status: zod.z.literal(200),
2563
+ json: HistoricalPatchSet
2564
+ })])
2565
+ }
2566
+ },
2567
+ /**
2568
+ * ONE module, as of a commit — including a commit that did not change it.
2569
+ *
2570
+ * `/history/commit` covers what a commit changed, which is the narrow case:
2571
+ * navigate the history pane to anything else and there would be nothing to
2572
+ * show. This answers "how did this module look at that point", which is the
2573
+ * question someone comparing two panes is actually asking. Immutable for the
2574
+ * same reason: what a commit left behind cannot change.
2575
+ */
2576
+ "/history/module": {
2577
+ GET: {
2578
+ req: {
2579
+ query: {
2580
+ commit_sha: onlyOneStringQueryParam,
2581
+ module_file_path: onlyOneStringQueryParam
2582
+ },
2583
+ cookies: {
2584
+ val_session: zod.z.string().optional()
2585
+ }
2586
+ },
2587
+ res: zod.z.union([unauthorizedResponse, notFoundResponse, zod.z.object({
2588
+ status: zod.z.literal(400),
2589
+ json: GenericError
2590
+ }), zod.z.object({
2591
+ status: zod.z.literal(500),
2592
+ json: GenericError.and(zod.z.object({
2593
+ kind: zod.z.string().optional()
2594
+ }))
2595
+ }), zod.z.object({
2596
+ status: zod.z.literal(200),
2597
+ json: zod.z.object({
2598
+ moduleFilePath: zod.z.string(),
2599
+ /**
2600
+ * Null when history has no record of this module at or before the
2601
+ * commit — it was last edited before recording started, or never.
2602
+ * Distinct from a module the commit deleted.
2603
+ */
2604
+ module: HistoricalModule.nullable()
2605
+ })
2606
+ })])
2607
+ }
2608
+ },
2609
+ /**
2610
+ * One file as it was at one commit.
2611
+ *
2612
+ * The lazy half: `/history/commit` NAMES a commit's binary files and this is
2613
+ * where their bytes come from, so nothing is fetched until an `<img>`
2614
+ * actually mounts. A file at a fixed commit cannot change, so the response is
2615
+ * immutable and the browser's own cache handles flipping between commits.
2616
+ */
2617
+ "/history/files": {
2618
+ GET: {
2619
+ req: {
2620
+ query: {
2621
+ commit_sha: onlyOneStringQueryParam,
2622
+ path: onlyOneStringQueryParam,
2623
+ remote: onlyOneStringQueryParam.optional()
2624
+ }
2625
+ },
2626
+ res: zod.z.union([unauthorizedResponse, notFoundResponse, zod.z.object({
2627
+ status: zod.z.literal(400),
2628
+ json: GenericError
2629
+ }), zod.z.object({
2630
+ status: zod.z.literal(200),
2631
+ body: zod.z["instanceof"](ReadableStream)
2632
+ })])
2633
+ }
2634
+ },
2635
+ // #endregion history
2396
2636
  "/files": {
2397
2637
  GET: {
2398
2638
  req: {
@@ -3029,6 +3269,354 @@ var urlOf = function urlOf() {
3029
3269
  return "".concat(route);
3030
3270
  };
3031
3271
 
3272
+ /**
3273
+ * Can this value, taken from one schema, be written into another?
3274
+ *
3275
+ * The question restore asks, and it is NOT the question validation asks.
3276
+ * Validation checks a value against the schema it belongs to. This compares two
3277
+ * schemas — the one a value was stored under and the one it is being written
3278
+ * into — because a restore moves a value across a schema change, and possibly
3279
+ * across paths.
3280
+ *
3281
+ * Three answers, not two. `unknown` is where the comparison genuinely cannot
3282
+ * decide, and it exists so that an editor is told "we are not sure" rather than
3283
+ * being given a confident answer we made up. Treating unknown as compatible
3284
+ * writes values that break; treating it as incompatible blocks restores that
3285
+ * would have been fine.
3286
+ */
3287
+
3288
+ var compatible = {
3289
+ status: "compatible"
3290
+ };
3291
+
3292
+ /**
3293
+ * `Array.isArray` cannot narrow `JsonArray` out of `Json`, because it is
3294
+ * `readonly Json[]` — so the check that reads naturally does not type. This
3295
+ * predicate is that check, said once.
3296
+ */
3297
+ function isJsonObject(value) {
3298
+ return _typeof(value) === "object" && value !== null && !Array.isArray(value);
3299
+ }
3300
+ function incompatible(reason) {
3301
+ return {
3302
+ status: "incompatible",
3303
+ reason: reason
3304
+ };
3305
+ }
3306
+
3307
+ /**
3308
+ * "an object", "a string".
3309
+ *
3310
+ * Schema type names are data — `object`, `array`, `image` — so an interpolated
3311
+ * "a" is wrong for three of them, in a sentence shown to editors the moment a
3312
+ * restore is refused.
3313
+ */
3314
+ function withArticle(type) {
3315
+ return /^[aeiou]/i.test(type) ? "an ".concat(type) : "a ".concat(type);
3316
+ }
3317
+
3318
+ /**
3319
+ * A sentence an editor can act on, for the popup that explains a refusal.
3320
+ *
3321
+ * Kept beside the reasons rather than in the UI so that adding a reason without
3322
+ * wording for it is a type error rather than a blank dialog.
3323
+ */
3324
+ function explainIncompatible(reason) {
3325
+ switch (reason.kind) {
3326
+ case "type-changed":
3327
+ return "This was ".concat(withArticle(reason.from), " and the field here is ").concat(withArticle(reason.to), ".");
3328
+ case "no-matching-variant":
3329
+ return "This does not match any of the shapes allowed here: ".concat(reason.variants.join(", "), ".");
3330
+ case "unknown-field":
3331
+ return "The old value has ".concat(reason.keys.length === 1 ? "a field" : "fields", " that no longer exist here: ").concat(reason.keys.join(", "), ".");
3332
+ case "missing-required":
3333
+ return "The field here needs ".concat(reason.keys.join(", "), ", which the old value does not have.");
3334
+ case "not-optional":
3335
+ return "The old value was empty, and this field cannot be empty.";
3336
+ case "literal-changed":
3337
+ return "This has to be exactly \"".concat(reason.to, "\", and the old value was \"").concat(reason.from, "\".");
3338
+ case "different-module":
3339
+ return "This points into ".concat(reason.to, ", and the old value pointed into ").concat(reason.from, ".");
3340
+ }
3341
+ }
3342
+
3343
+ /**
3344
+ * The variant of a union that a value actually is.
3345
+ *
3346
+ * `undefined` when the value does not identify itself — which is a real answer,
3347
+ * not a failure: it is what makes the caller return `unknown` rather than guess.
3348
+ */
3349
+ function variantOf(schema, value) {
3350
+ if (schema.type !== "union") {
3351
+ return undefined;
3352
+ }
3353
+ // String union: the value IS the discriminator.
3354
+ if (typeof schema.key !== "string") {
3355
+ if (typeof value !== "string") {
3356
+ return undefined;
3357
+ }
3358
+ return schema.items.find(function (item) {
3359
+ return item.type === "literal" && item.value === value;
3360
+ });
3361
+ }
3362
+ // Object union: the discriminator is a literal at `key`.
3363
+ var discriminator = schema.key;
3364
+ if (!isJsonObject(value)) {
3365
+ return undefined;
3366
+ }
3367
+ var tag = value[discriminator];
3368
+ if (typeof tag !== "string") {
3369
+ return undefined;
3370
+ }
3371
+ return schema.items.find(function (item) {
3372
+ if (item.type !== "object") {
3373
+ return false;
3374
+ }
3375
+ var keySchema = item.items[discriminator];
3376
+ return (keySchema === null || keySchema === void 0 ? void 0 : keySchema.type) === "literal" && keySchema.value === tag;
3377
+ });
3378
+ }
3379
+ function describeVariant(schema, key) {
3380
+ if (schema.type === "literal") {
3381
+ return schema.value;
3382
+ }
3383
+ if (schema.type === "object") {
3384
+ var tag = schema.items[key];
3385
+ if ((tag === null || tag === void 0 ? void 0 : tag.type) === "literal") {
3386
+ return tag.value;
3387
+ }
3388
+ }
3389
+ return schema.type;
3390
+ }
3391
+ function checkCompatibility(from, to) {
3392
+ // Emptiness first: a null that lands in a field which cannot be empty is a
3393
+ // break regardless of what the two schemas otherwise say.
3394
+ if (from.value === null) {
3395
+ return to.schema.opt ? compatible : incompatible({
3396
+ kind: "not-optional"
3397
+ });
3398
+ }
3399
+
3400
+ // A changed union is not itself disqualifying. What matters is whether the
3401
+ // variant being restored still has somewhere to go — comparing the unions
3402
+ // wholesale would refuse a restore that is perfectly safe.
3403
+ if (to.schema.type === "union") {
3404
+ var key = typeof to.schema.key === "string" ? to.schema.key : "";
3405
+ var _iterator = _createForOfIteratorHelper(to.schema.items),
3406
+ _step;
3407
+ try {
3408
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
3409
+ var variant = _step.value;
3410
+ if (checkCompatibility(from, {
3411
+ schema: variant
3412
+ }).status === "compatible") {
3413
+ return compatible;
3414
+ }
3415
+ }
3416
+ } catch (err) {
3417
+ _iterator.e(err);
3418
+ } finally {
3419
+ _iterator.f();
3420
+ }
3421
+ return incompatible({
3422
+ kind: "no-matching-variant",
3423
+ variants: to.schema.items.map(function (item) {
3424
+ return describeVariant(item, key);
3425
+ })
3426
+ });
3427
+ }
3428
+
3429
+ // Coming FROM a union into something that is not one: only the variant this
3430
+ // value actually is matters, so narrow before comparing.
3431
+ if (from.schema.type === "union") {
3432
+ var _variant = variantOf(from.schema, from.value);
3433
+ if (_variant === undefined) {
3434
+ return {
3435
+ status: "unknown",
3436
+ message: "The old value does not say which shape it is, so we cannot tell whether it fits here."
3437
+ };
3438
+ }
3439
+ return checkCompatibility({
3440
+ schema: _variant,
3441
+ value: from.value
3442
+ }, to);
3443
+ }
3444
+ if (from.schema.type !== to.schema.type) {
3445
+ return incompatible({
3446
+ kind: "type-changed",
3447
+ from: from.schema.type,
3448
+ to: to.schema.type
3449
+ });
3450
+ }
3451
+ switch (to.schema.type) {
3452
+ case "literal":
3453
+ {
3454
+ var fromValue = from.schema.type === "literal" ? from.schema.value : String(from.value);
3455
+ return fromValue === to.schema.value ? compatible : incompatible({
3456
+ kind: "literal-changed",
3457
+ from: fromValue,
3458
+ to: to.schema.value
3459
+ });
3460
+ }
3461
+ case "object":
3462
+ {
3463
+ if (from.schema.type !== "object" || !isJsonObject(from.value)) {
3464
+ return {
3465
+ status: "unknown",
3466
+ message: "The old value is not shaped like an object."
3467
+ };
3468
+ }
3469
+ var _fromValue = from.value;
3470
+ var target = to.schema.items;
3471
+ var source = from.schema.items;
3472
+
3473
+ // Keys the old value carries that the field no longer has. Writing them
3474
+ // would put data into a schema with nowhere to hold it.
3475
+ var extra = Object.keys(source).filter(function (key) {
3476
+ return !(key in target);
3477
+ });
3478
+ if (extra.length > 0) {
3479
+ return incompatible({
3480
+ kind: "unknown-field",
3481
+ keys: extra
3482
+ });
3483
+ }
3484
+
3485
+ // Keys the field requires that the old value cannot supply.
3486
+ var missing = Object.keys(target).filter(function (key) {
3487
+ return !target[key].opt && !(key in source);
3488
+ });
3489
+ if (missing.length > 0) {
3490
+ return incompatible({
3491
+ kind: "missing-required",
3492
+ keys: missing
3493
+ });
3494
+ }
3495
+ var unknown = null;
3496
+ for (var _i = 0, _Object$entries = Object.entries(target); _i < _Object$entries.length; _i++) {
3497
+ var _fromValue$_key;
3498
+ var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
3499
+ _key = _Object$entries$_i[0],
3500
+ targetItem = _Object$entries$_i[1];
3501
+ var sourceItem = source[_key];
3502
+ if (sourceItem === undefined) {
3503
+ continue; // optional and absent — already allowed above
3504
+ }
3505
+ var res = checkCompatibility({
3506
+ schema: sourceItem,
3507
+ value: (_fromValue$_key = _fromValue[_key]) !== null && _fromValue$_key !== void 0 ? _fromValue$_key : null
3508
+ }, {
3509
+ schema: targetItem
3510
+ });
3511
+ if (res.status === "incompatible") {
3512
+ return res;
3513
+ }
3514
+ if (res.status === "unknown" && unknown === null) {
3515
+ unknown = res;
3516
+ }
3517
+ }
3518
+ return unknown !== null && unknown !== void 0 ? unknown : compatible;
3519
+ }
3520
+ case "array":
3521
+ {
3522
+ if (from.schema.type !== "array" || !Array.isArray(from.value)) {
3523
+ return {
3524
+ status: "unknown",
3525
+ message: "The old value is not a list."
3526
+ };
3527
+ }
3528
+ // An empty list has nothing to probe the item schema with. The item
3529
+ // TYPES still have to agree, and that much can be decided without a
3530
+ // value - so decide it rather than passing an empty list as proof.
3531
+ if (from.value.length === 0) {
3532
+ return from.schema.item.type === to.schema.item.type ? compatible : incompatible({
3533
+ kind: "type-changed",
3534
+ from: from.schema.item.type,
3535
+ to: to.schema.item.type
3536
+ });
3537
+ }
3538
+ var _unknown = null;
3539
+ var _iterator2 = _createForOfIteratorHelper(from.value),
3540
+ _step2;
3541
+ try {
3542
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
3543
+ var item = _step2.value;
3544
+ var _res = checkCompatibility({
3545
+ schema: from.schema.item,
3546
+ value: item
3547
+ }, {
3548
+ schema: to.schema.item
3549
+ });
3550
+ if (_res.status === "incompatible") {
3551
+ return _res;
3552
+ }
3553
+ if (_res.status === "unknown" && _unknown === null) {
3554
+ _unknown = _res;
3555
+ }
3556
+ }
3557
+ } catch (err) {
3558
+ _iterator2.e(err);
3559
+ } finally {
3560
+ _iterator2.f();
3561
+ }
3562
+ return _unknown !== null && _unknown !== void 0 ? _unknown : compatible;
3563
+ }
3564
+ case "record":
3565
+ {
3566
+ if (from.schema.type !== "record" || !isJsonObject(from.value)) {
3567
+ return {
3568
+ status: "unknown",
3569
+ message: "The old value is not a record."
3570
+ };
3571
+ }
3572
+ var _unknown2 = null;
3573
+ for (var _i2 = 0, _Object$values = Object.values(from.value); _i2 < _Object$values.length; _i2++) {
3574
+ var entry = _Object$values[_i2];
3575
+ var _res2 = checkCompatibility({
3576
+ schema: from.schema.item,
3577
+ value: entry !== null && entry !== void 0 ? entry : null
3578
+ }, {
3579
+ schema: to.schema.item
3580
+ });
3581
+ if (_res2.status === "incompatible") {
3582
+ return _res2;
3583
+ }
3584
+ if (_res2.status === "unknown" && _unknown2 === null) {
3585
+ _unknown2 = _res2;
3586
+ }
3587
+ }
3588
+ return _unknown2 !== null && _unknown2 !== void 0 ? _unknown2 : compatible;
3589
+ }
3590
+ case "keyOf":
3591
+ {
3592
+ // A key is only meaningful against the module it indexes.
3593
+ if (from.schema.type !== "keyOf") {
3594
+ return compatible;
3595
+ }
3596
+ return from.schema.path === to.schema.path ? compatible : incompatible({
3597
+ kind: "different-module",
3598
+ from: from.schema.path,
3599
+ to: to.schema.path
3600
+ });
3601
+ }
3602
+ case "richtext":
3603
+ {
3604
+ // The options decide which nodes are legal, and comparing them properly
3605
+ // means comparing node trees against them. Not yet done, and saying so is
3606
+ // better than a confident answer we cannot back.
3607
+ return {
3608
+ status: "unknown",
3609
+ message: "Rich text can be restored, but we cannot yet confirm every mark and block is still allowed here."
3610
+ };
3611
+ }
3612
+ default:
3613
+ // Primitives and media: same type is the whole test. Constraints
3614
+ // (maxLength, accept, …) are validated on write, which reports them
3615
+ // better than a schema comparison could.
3616
+ return compatible;
3617
+ }
3618
+ }
3619
+
3032
3620
  /**
3033
3621
  * The newest commit the client has been told about, or `null` if none.
3034
3622
  *
@@ -4534,6 +5122,13 @@ function isSkippableFixCode(fix) {
4534
5122
  // rewriting the module) and saving does not repair it either, so surface
4535
5123
  // it: the message names the command (`val validate --fix`) that does.
4536
5124
  return false;
5125
+ case "external:upload":
5126
+ // Same shape as `jsonValues:extract-entry` — an entry inlined in a
5127
+ // `.val.ts` that belongs elsewhere — and more emphatically not skippable.
5128
+ // Fixing it writes to a live store, which is why even the CLI does it only
5129
+ // under `val external upload` and never under a blanket `--fix`. Surfacing
5130
+ // it is the whole point: someone has to decide.
5131
+ return false;
4537
5132
  default:
4538
5133
  {
4539
5134
  return false;
@@ -4574,14 +5169,21 @@ function filterBlockingValidationErrors(validationErrors, schemas, sources) {
4574
5169
  }
4575
5170
 
4576
5171
  exports.Api = Api;
5172
+ exports.BinaryFileRef = BinaryFileRef;
4577
5173
  exports.CodeOptions = CodeOptions;
4578
5174
  exports.ColorOptions = ColorOptions;
4579
5175
  exports.DEFAULT_ERROR_MESSAGE = DEFAULT_ERROR_MESSAGE;
4580
5176
  exports.DateOptions = DateOptions;
4581
5177
  exports.FileOptions = FileOptions;
5178
+ exports.HistoricalCommit = HistoricalCommit;
5179
+ exports.HistoricalModule = HistoricalModule;
5180
+ exports.HistoricalPatchSet = HistoricalPatchSet;
5181
+ exports.HistoryError = HistoryError;
4582
5182
  exports.ImageEncodeOption = ImageEncodeOption;
4583
5183
  exports.ImageOptions = ImageOptions;
5184
+ exports.JSONValue = JSONValue;
4584
5185
  exports.JSON_ENTRIES_BATCH_MAX = JSON_ENTRIES_BATCH_MAX;
5186
+ exports.ModuleComparison = ModuleComparison;
4585
5187
  exports.ParentRef = ParentRef$1;
4586
5188
  exports.Patch = Patch;
4587
5189
  exports.PatchBlock = PatchBlock;
@@ -4599,6 +5201,7 @@ exports.RemirrorOrderedList = RemirrorOrderedList;
4599
5201
  exports.RemirrorParagraph = RemirrorParagraph;
4600
5202
  exports.RemirrorText = RemirrorText;
4601
5203
  exports.RemirrorTextMark = RemirrorTextMark;
5204
+ exports.RestoreVerdict = RestoreVerdict;
4602
5205
  exports.RichTextOptions = RichTextOptions;
4603
5206
  exports.SerializedArraySchema = SerializedArraySchema;
4604
5207
  exports.SerializedBooleanSchema = SerializedBooleanSchema;
@@ -4616,6 +5219,7 @@ exports.SerializedRecordSchema = SerializedRecordSchema;
4616
5219
  exports.SerializedRichTextSchema = SerializedRichTextSchema;
4617
5220
  exports.SerializedRouteSchema = SerializedRouteSchema;
4618
5221
  exports.SerializedSchema = SerializedSchema;
5222
+ exports.SerializedSettingsSchema = SerializedSettingsSchema;
4619
5223
  exports.SerializedStringSchema = SerializedStringSchema;
4620
5224
  exports.SerializedUnionSchema = SerializedUnionSchema;
4621
5225
  exports.SharedValConfig = SharedValConfig;
@@ -4634,10 +5238,12 @@ exports.buildEmptyAtPathPatch = buildEmptyAtPathPatch;
4634
5238
  exports.buildFileRefValue = buildFileRefValue;
4635
5239
  exports.buildImageGalleryPatch = buildImageGalleryPatch;
4636
5240
  exports.buildRemoveImageGalleryEntryPatch = buildRemoveImageGalleryEntryPatch;
5241
+ exports.checkCompatibility = checkCompatibility;
4637
5242
  exports.createRegExpFromPattern = createRegExpFromPattern;
4638
5243
  exports.createValClient = createValClient;
4639
5244
  exports.describeContainerAtPath = describeContainerAtPath;
4640
5245
  exports.emptyOf = emptyOf;
5246
+ exports.explainIncompatible = explainIncompatible;
4641
5247
  exports.filterBlockingValidationErrors = filterBlockingValidationErrors;
4642
5248
  exports.filterRoutesByPatterns = filterRoutesByPatterns;
4643
5249
  exports.findSimilar = findSimilar;