@valbuild/shared 0.145.1 → 0.146.2

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.
@@ -768,14 +768,16 @@ var VAL_ENABLE_COOKIE_NAME = core.Internal.VAL_ENABLE_COOKIE_NAME;
768
768
  * byte-identical each time, because the query and the session are fixed for the
769
769
  * length of a request. This is what makes the second and third call free.
770
770
  *
771
- * ## Why the scope is the REQUEST and can never be wider
771
+ * ## Why the scope is the REQUEST
772
772
  *
773
- * The memoised response is `own_patch_groups_only: true` — it contains the
774
- * CALLER'S OWN unpublished work, resolved from their session cookie. A memo
775
- * that outlived the request would serve one author's staged edits to the next
776
- * visitor, which is the exact failure `own_patch_groups_only` exists to
777
- * prevent, and it would be invisible: the draft would simply render somebody
778
- * else's half-finished sentence.
773
+ * The memoised response is a DRAFT -- unpublished work, read with the caller's
774
+ * session cookie. Every caller allowed to read drafts gets the same draft (patch
775
+ * groups, which made it per author, are gone), but whether a caller is allowed
776
+ * at all is decided per session. A memo that outlived the request would hand
777
+ * the draft to the next visitor without asking, and it would be invisible: the
778
+ * page would simply render somebody's half-finished sentence. Sharing it across
779
+ * requests is possible now, keyed on the chain's head, and would be its own
780
+ * measured change.
779
781
  *
780
782
  * So this module holds NO state of its own. The caller supplies the box, and
781
783
  * the box has to come from something the framework already scopes to the
@@ -1616,7 +1618,7 @@ var ValConfig = zod.z.object({
1616
1618
  gitCommit: zod.z.string().optional(),
1617
1619
  gitBranch: zod.z.string().optional()
1618
1620
  });
1619
- var ValidationFixZ = zod.z.union([zod.z.literal("image:add-metadata"), zod.z.literal("image:check-metadata"), zod.z.literal("image:check-remote"), zod.z.literal("image:upload-remote"), zod.z.literal("image:download-remote"), zod.z.literal("images:check-remote"), zod.z.literal("images:upload-remote"), zod.z.literal("file:add-metadata"), zod.z.literal("video:add-metadata"), zod.z.literal("video:upload-remote"), zod.z.literal("video:download-remote"), zod.z.literal("videos:add-metadata"), zod.z.literal("videos:check-remote"), zod.z.literal("videos:upload-remote"), zod.z.literal("file:check-metadata"), zod.z.literal("file:check-remote"), zod.z.literal("file:upload-remote"), zod.z.literal("file:download-remote"), zod.z.literal("files:check-remote"), zod.z.literal("files:upload-remote"), zod.z.literal("keyof:check-keys"), zod.z.literal("router:check-route"), zod.z.literal("locale:check-locale"), zod.z.literal("record:fill-keys"), zod.z.literal("images:check-unique-folder"), zod.z.literal("files:check-unique-folder"), zod.z.literal("images:check-all-files"), zod.z.literal("files:check-all-files"), zod.z.literal("videos:check-unique-folder"), zod.z.literal("videos:check-all-files"), zod.z.literal("jsonValues:extract-entry"), zod.z.literal("view:check-module")]);
1621
+ var ValidationFixZ = zod.z.union([zod.z.literal("image:add-metadata"), zod.z.literal("image:check-metadata"), zod.z.literal("image:check-remote"), zod.z.literal("image:upload-remote"), zod.z.literal("image:download-remote"), zod.z.literal("images:check-remote"), zod.z.literal("images:upload-remote"), zod.z.literal("file:add-metadata"), zod.z.literal("video:add-metadata"), zod.z.literal("video:upload-remote"), zod.z.literal("video:download-remote"), zod.z.literal("videos:add-metadata"), zod.z.literal("videos:check-remote"), zod.z.literal("videos:upload-remote"), zod.z.literal("file:check-metadata"), zod.z.literal("file:check-remote"), zod.z.literal("file:upload-remote"), zod.z.literal("file:download-remote"), zod.z.literal("files:check-remote"), zod.z.literal("files:upload-remote"), zod.z.literal("keyof:check-keys"), zod.z.literal("image:check-gallery"), zod.z.literal("file:check-gallery"), zod.z.literal("video:check-gallery"), zod.z.literal("router:check-route"), zod.z.literal("locale:check-locale"), zod.z.literal("record:fill-keys"), zod.z.literal("images:check-unique-folder"), zod.z.literal("files:check-unique-folder"), zod.z.literal("images:check-all-files"), zod.z.literal("files:check-all-files"), zod.z.literal("videos:check-unique-folder"), zod.z.literal("videos:check-all-files"), zod.z.literal("jsonValues:extract-entry"), zod.z.literal("view:check-module")]);
1620
1622
  var ValidationError = zod.z.object({
1621
1623
  message: zod.z.string(),
1622
1624
  value: zod.z.unknown().optional(),
@@ -1733,21 +1735,6 @@ var StatProposal = zod.z.object({
1733
1735
  /** `val/p/<name>`. */
1734
1736
  branch: zod.z.string()
1735
1737
  });
1736
-
1737
- /**
1738
- * A patch group: the set of patches one user has chosen to publish.
1739
- *
1740
- * Not a patch *set* — a patch set is computed from the schema and says which
1741
- * patches must move together; a patch group is curated and says which ones this
1742
- * user wants live. See `docs/independent-publish/PLAN.md`.
1743
- */
1744
- var PatchGroup = zod.z.object({
1745
- patchGroupId: zod.z.string(),
1746
- authorId: zod.z.string().nullable(),
1747
- createdAt: zod.z.string(),
1748
- publishedAt: zod.z.string().nullable(),
1749
- patchIds: zod.z.array(PatchId)
1750
- });
1751
1738
  var GenericPatchError = zod.z.union([zod.z.object({
1752
1739
  patchId: PatchId,
1753
1740
  message: zod.z.string(),
@@ -2256,14 +2243,6 @@ var Api = {
2256
2243
  * highest version. Optional, for the same reason `headPatchId` is.
2257
2244
  */
2258
2245
  headVersion: zod.z.number().optional(),
2259
- /**
2260
- * Every group on the branch and which listed patches it holds,
2261
- * read with the list and `headVersion`. What a Studio's view of
2262
- * the groups is taken from, so a stage or an unstage made in
2263
- * another browser reaches it with the next stat. Absent where
2264
- * the content service has no groups.
2265
- */
2266
- patchGroups: zod.z.array(PatchGroup).optional(),
2267
2246
  commits: zod.z.array(ValCommit),
2268
2247
  /**
2269
2248
  * The publishes the content service knows about.
@@ -2560,24 +2539,6 @@ var Api = {
2560
2539
  id: zod.z.array(PatchId).min(1, "At least one patch id is required")
2561
2540
  },
2562
2541
  body: zod.z.object({
2563
- /*
2564
- * Patches that are NOT being deleted but must lose their patch
2565
- * group membership because these are.
2566
- *
2567
- * Deleting a patch out of the middle of a patch set leaves every
2568
- * group holding the rest with a non-prefix intersection, and a
2569
- * prefix is the one invariant a group has. Which patches those are
2570
- * needs the content schema, so the client computes the forward
2571
- * closure and sends it; the content API drops the memberships
2572
- * without touching the patches.
2573
- *
2574
- * In the BODY while the ids to delete are in the query, because
2575
- * this list is unbounded in the same way and the query string is
2576
- * already chunked to fit. Optional throughout: `fs` mode has no
2577
- * groups, and a client that predates this is deleting correctly,
2578
- * just not repairing.
2579
- */
2580
- unstagePatchIds: zod.z.array(PatchId).optional(),
2581
2542
  /*
2582
2543
  * Answer with `{ deleted, headVersion }` rather than the bare id
2583
2544
  * array. Asked for rather than always sent, because the array is
@@ -2619,16 +2580,7 @@ var Api = {
2619
2580
  path: ModuleFilePath,
2620
2581
  patchId: PatchId,
2621
2582
  patch: zod.z.any() // TODO: this should be Patch instead - we got a weird validation error: although input looks good, it still does not accept objects as values... Which it should do via the z.record(JSONValue) type
2622
- })),
2623
- // Patch group membership. Sent with the patch itself, in one request, so
2624
- // there is no window in which a patch exists but belongs to no group.
2625
- // Absent means "the caller does not know about groups" - the server then
2626
- // behaves exactly as before.
2627
- patchGroupId: zod.z.string().nullish(),
2628
- // What has to come with the patch: other patch ids that must join the
2629
- // same group for it to stay applicable. Computed on the client, which
2630
- // is the only side that has the schema needed to derive patch sets.
2631
- withPatchIds: zod.z.array(PatchId).optional()
2583
+ }))
2632
2584
  }),
2633
2585
  cookies: {
2634
2586
  val_session: zod.z.string().optional()
@@ -2670,7 +2622,6 @@ var Api = {
2670
2622
  error: PlanRefusalError,
2671
2623
  newPatchIds: zod.z.array(PatchId),
2672
2624
  parentRef: ParentRef,
2673
- patchGroupId: zod.z.string().optional(),
2674
2625
  headVersion: zod.z.number().optional()
2675
2626
  })
2676
2627
  }), zod.z.object({
@@ -2678,7 +2629,6 @@ var Api = {
2678
2629
  json: zod.z.object({
2679
2630
  newPatchIds: zod.z.array(PatchId),
2680
2631
  parentRef: ParentRef,
2681
- patchGroupId: zod.z.string().optional(),
2682
2632
  /**
2683
2633
  * The chain version the last of these patches made, comparable with
2684
2634
  * `headVersion` on `/stat`. A client keeps the highest version, so a
@@ -2693,16 +2643,7 @@ var Api = {
2693
2643
  req: {
2694
2644
  query: {
2695
2645
  patch_id: zod.z.array(PatchId).optional(),
2696
- exclude_patch_ops: onlyOneBooleanQueryParam.optional(),
2697
- /**
2698
- * Include the patch group annotation in the answer.
2699
- *
2700
- * Off by default because the client CHUNKS this request — a long
2701
- * chain is several round trips — and the groups are a property of the
2702
- * branch, identical in every chunk. Asking on each one is N identical
2703
- * lookups against the content API to learn the same thing.
2704
- */
2705
- include_patch_groups: onlyOneBooleanQueryParam.optional()
2646
+ exclude_patch_ops: onlyOneBooleanQueryParam.optional()
2706
2647
  },
2707
2648
  cookies: {
2708
2649
  val_session: zod.z.string().optional()
@@ -2731,12 +2672,8 @@ var Api = {
2731
2672
  authorId: zod.z.string().nullable(),
2732
2673
  appliedAt: zod.z.object({
2733
2674
  commitSha: zod.z.string()
2734
- }).nullable(),
2735
- // Which patch groups hold this patch. Optional so that FS mode, and
2736
- // any server that predates patch groups, can leave it out.
2737
- patchGroupIds: zod.z.array(zod.z.string()).optional()
2675
+ }).nullable()
2738
2676
  })),
2739
- patchGroups: zod.z.array(PatchGroup).optional(),
2740
2677
  baseSha: zod.z.string(),
2741
2678
  error: GenericError.optional(),
2742
2679
  errors: zod.z.record(PatchId, GenericError).optional()
@@ -2744,111 +2681,6 @@ var Api = {
2744
2681
  })])
2745
2682
  }
2746
2683
  },
2747
- // Patch group membership. Both are idempotent set operations, and both take an
2748
- // already-closed set: the client computes the prefix closure (staging) or the
2749
- // forward closure (unstaging) because only the client has the schema needed to
2750
- // derive patch sets. See `docs/independent-publish/PLAN.md`.
2751
- "/patch-groups/~/patches": {
2752
- PUT: {
2753
- req: {
2754
- body: zod.z.object({
2755
- /**
2756
- * The group to stage into. Absent means the caller's open group on
2757
- * the branch, created if there is none — which is what the Studio
2758
- * sends, so a stage never has to wait for a group to exist.
2759
- */
2760
- patchGroupId: zod.z.string().optional(),
2761
- /** What the user asked to stage. */
2762
- patchIds: zod.z.array(PatchId),
2763
- /*
2764
- * What has to come with it, because the staged patches are written on
2765
- * top of it.
2766
- *
2767
- * The content API records membership as `explicit` or `dependency`
2768
- * and reads what it is not told about as a dependency — so folding
2769
- * the two halves into `patchIds` files the patch someone clicked as
2770
- * something the closure dragged in.
2771
- */
2772
- withPatchIds: zod.z.array(PatchId).optional()
2773
- }).refine(function (body) {
2774
- var _body$withPatchIds$le, _body$withPatchIds;
2775
- return body.patchIds.length > 0 || ((_body$withPatchIds$le = (_body$withPatchIds = body.withPatchIds) === null || _body$withPatchIds === void 0 ? void 0 : _body$withPatchIds.length) !== null && _body$withPatchIds$le !== void 0 ? _body$withPatchIds$le : 0) > 0;
2776
- },
2777
- // Either half alone is a real request: a stage of only what came with
2778
- // it happens when the deferred queue's scope filter drops everything
2779
- // the user originally clicked.
2780
- "At least one patch id is required"),
2781
- cookies: {
2782
- val_session: zod.z.string().optional()
2783
- }
2784
- },
2785
- res: zod.z.union([unauthorizedResponse, zod.z.object({
2786
- status: zod.z.literal(403),
2787
- json: GenericError
2788
- }), zod.z.object({
2789
- // The group has already been published, so its membership is frozen.
2790
- status: zod.z.literal(409),
2791
- json: GenericError
2792
- }), zod.z.object({
2793
- status: zod.z.literal(500),
2794
- json: GenericError
2795
- }), zod.z.object({
2796
- status: zod.z.literal(200),
2797
- json: zod.z.object({
2798
- /** `null` for an unstage when the caller has no open group. */
2799
- patchGroupId: zod.z.string().nullable(),
2800
- patchIds: zod.z.array(PatchId),
2801
- /**
2802
- * The chain version the change committed at. The client keeps its
2803
- * own view of the change until a stat at least this new shows it.
2804
- * Absent from a content service that predates it.
2805
- */
2806
- headVersion: zod.z.number().optional()
2807
- })
2808
- })])
2809
- },
2810
- DELETE: {
2811
- req: {
2812
- body: zod.z.object({
2813
- /** See the PUT. Absent with no open group is a no-op. */
2814
- patchGroupId: zod.z.string().optional(),
2815
- /** What the user asked to unstage. */
2816
- patchIds: zod.z.array(PatchId),
2817
- /** What has to go with it: everything built on top of it. */
2818
- withPatchIds: zod.z.array(PatchId).optional()
2819
- }).refine(function (body) {
2820
- var _body$withPatchIds$le2, _body$withPatchIds2;
2821
- return body.patchIds.length > 0 || ((_body$withPatchIds$le2 = (_body$withPatchIds2 = body.withPatchIds) === null || _body$withPatchIds2 === void 0 ? void 0 : _body$withPatchIds2.length) !== null && _body$withPatchIds$le2 !== void 0 ? _body$withPatchIds$le2 : 0) > 0;
2822
- }, "At least one patch id is required"),
2823
- cookies: {
2824
- val_session: zod.z.string().optional()
2825
- }
2826
- },
2827
- res: zod.z.union([unauthorizedResponse, zod.z.object({
2828
- status: zod.z.literal(403),
2829
- json: GenericError
2830
- }), zod.z.object({
2831
- status: zod.z.literal(409),
2832
- json: GenericError
2833
- }), zod.z.object({
2834
- status: zod.z.literal(500),
2835
- json: GenericError
2836
- }), zod.z.object({
2837
- status: zod.z.literal(200),
2838
- json: zod.z.object({
2839
- /** `null` for an unstage when the caller has no open group. */
2840
- patchGroupId: zod.z.string().nullable(),
2841
- patchIds: zod.z.array(PatchId),
2842
- /**
2843
- * The chain version the change committed at. The client keeps its
2844
- * own view of the change until a stat at least this new shows it.
2845
- * Absent from a content service that predates it.
2846
- */
2847
- headVersion: zod.z.number().optional()
2848
- })
2849
- })])
2850
- }
2851
- },
2852
2684
  "/schema": {
2853
2685
  GET: {
2854
2686
  req: {
@@ -2885,34 +2717,7 @@ var Api = {
2885
2717
  validate_sources: onlyOneBooleanQueryParam.optional(),
2886
2718
  validate_binary_files: onlyOneBooleanQueryParam.optional(),
2887
2719
  exclude_patches: onlyOneBooleanQueryParam.optional(),
2888
- apply_patches: onlyOneBooleanQueryParam.optional(),
2889
- /**
2890
- * Apply ONLY these patches, rather than everything pending.
2891
- *
2892
- * This is what makes a draft-mode render match the caller's staged
2893
- * view. Without it `/sources/~` replays every pending patch on the
2894
- * branch, so a server-rendered preview shows other people's
2895
- * unpublished work — the one thing independent publish is for.
2896
- *
2897
- * Omitted means everything pending, which is the behaviour every
2898
- * existing caller has and must keep. An EMPTY array is a different
2899
- * answer and is honoured as one: a group holding nothing renders
2900
- * base.
2901
- */
2902
- patch_id: zod.z.array(PatchId).optional(),
2903
- /**
2904
- * Apply only the patches in the CALLER'S OWN patch groups.
2905
- *
2906
- * The server resolves them from the session, so a draft render does
2907
- * not have to know its own group ids — which it cannot, since it has
2908
- * no client state. This is what makes `fetchVal` in draft mode show
2909
- * the person their own staged work rather than everybody's.
2910
- *
2911
- * Ignored when `patch_id` is given: an explicit list is a caller that
2912
- * already knows what it wants, and silently intersecting the two
2913
- * would make the narrower one invisible.
2914
- */
2915
- own_patch_groups_only: onlyOneBooleanQueryParam.optional()
2720
+ apply_patches: onlyOneBooleanQueryParam.optional()
2916
2721
  },
2917
2722
  cookies: {
2918
2723
  val_session: zod.z.string().optional()
@@ -3066,24 +2871,72 @@ var Api = {
3066
2871
  })])
3067
2872
  }
3068
2873
  },
2874
+ /**
2875
+ * Publish selected (`docs/plans/remove-patch-groups.md`, Phase 4), its first
2876
+ * half, on the site's own server: the changes picked in Review, already
2877
+ * closed into whole patch sets (`selectionClosure`), moved into a proposal
2878
+ * of kind `publish` and saved there -- prepared over the site's head, which
2879
+ * the proposal is based on, so a publish landing in between is refused
2880
+ * rather than reverted. The second half is that proposal's Publish, pressed
2881
+ * as any proposal's is (`publishProposal`); this server answers its checks
2882
+ * and press with what the merge moves, as the proposal's own address would.
2883
+ */
2884
+ "/publish-selected": {
2885
+ POST: {
2886
+ req: {
2887
+ body: zod.z.object({
2888
+ /** The changes to publish, whole patch sets, in any order: a move keeps the chain's. */
2889
+ patchIds: zod.z.array(PatchId).min(1),
2890
+ /** What the merged proposal is listed as. */
2891
+ displayName: zod.z.string().min(1).max(200),
2892
+ /** The press's own id: the same press sent again is the same proposal. */
2893
+ requestId: zod.z.string().regex(/^[A-Za-z0-9_-]{1,100}$/)
2894
+ }),
2895
+ cookies: {
2896
+ val_session: zod.z.string().optional()
2897
+ }
2898
+ },
2899
+ res: zod.z.union([unauthorizedResponse, zod.z.object({
2900
+ status: zod.z.literal(400),
2901
+ json: GenericError
2902
+ }),
2903
+ /**
2904
+ * Refused: the site moved (`headMoved`: pressing again prepares over
2905
+ * the new head), a change went, or this deployment cannot.
2906
+ */
2907
+ zod.z.object({
2908
+ status: zod.z.literal(409),
2909
+ json: GenericError.extend({
2910
+ headMoved: zod.z.literal(true).optional()
2911
+ })
2912
+ }), zod.z.object({
2913
+ status: zod.z.literal(500),
2914
+ json: GenericError
2915
+ }),
2916
+ /**
2917
+ * The proposal was made and its changes moved into it, and the save
2918
+ * there failed: they are safe in it, which `proposal` names.
2919
+ */
2920
+ zod.z.object({
2921
+ status: zod.z.literal(502),
2922
+ json: GenericError.extend({
2923
+ proposal: zod.z.string()
2924
+ })
2925
+ }), zod.z.object({
2926
+ status: zod.z.literal(200),
2927
+ json: zod.z.object({
2928
+ /** The proposal to publish next. */
2929
+ proposal: zod.z.string()
2930
+ })
2931
+ })])
2932
+ }
2933
+ },
3069
2934
  "/save": {
3070
2935
  POST: {
3071
2936
  req: {
3072
2937
  body: zod.z.object({
3073
2938
  message: zod.z.string().optional(),
3074
2939
  patchIds: zod.z.array(PatchId),
3075
- /*
3076
- * The patch group this commit EMPTIES, if it empties one.
3077
- *
3078
- * Forwarded to the content API, which closes the group it names —
3079
- * and closes it unconditionally, without checking that the commit
3080
- * shipped all of it. So the client sends it only when the publish
3081
- * accounts for every patch the group still holds; a partial publish
3082
- * omits it and the group stays open with the rest in it.
3083
- *
3084
- * Optional, and absent in `fs` mode, which has no groups.
3085
- */
3086
- patchGroupId: zod.z.string().optional(),
3087
2940
  /*
3088
2941
  * The newest commit this client knew about when it decided.
3089
2942
  *
@@ -3192,19 +3045,6 @@ var Api = {
3192
3045
  */
3193
3046
  headVersion: zod.z.number().optional()
3194
3047
  })
3195
- }),
3196
- /*
3197
- * The named patch group is not this caller's, or the lookup that would
3198
- * say so failed.
3199
- *
3200
- * Publishing may CLOSE a group, and closing somebody else's leaves
3201
- * their pending work in a closed group and in no open one. So `/save`
3202
- * runs the same ownership check stage and unstage do, and refuses the
3203
- * same way — see `refuseUnlessOwn`.
3204
- */
3205
- zod.z.object({
3206
- status: zod.z.literal(403),
3207
- json: GenericError
3208
3048
  }), zod.z.object({
3209
3049
  status: zod.z.literal(500),
3210
3050
  json: GenericError
@@ -3222,8 +3062,7 @@ var Api = {
3222
3062
  zod.z.object({
3223
3063
  message: zod.z.string(),
3224
3064
  publishRefusal: PublishRefusal
3225
- }), /** A group that has already shipped. See the 403 above. */
3226
- zod.z.object({
3065
+ }), zod.z.object({
3227
3066
  message: zod.z.string()
3228
3067
  }), zod.z.object({
3229
3068
  message: zod.z.string(),
@@ -3242,22 +3081,6 @@ var Api = {
3242
3081
  zod.z.object({
3243
3082
  message: zod.z.string(),
3244
3083
  headMoved: zod.z.literal(true)
3245
- }),
3246
- /**
3247
- * The group this commit named has already been published.
3248
- *
3249
- * A third thing sharing 409, and flagged for the same reason
3250
- * `headMoved` is: read as a plain not-fast-forward it looks
3251
- * retryable, and it is the opposite — the id will never be writable
3252
- * again, so retrying reproduces it forever.
3253
- *
3254
- * It means this author published from somewhere else. The client
3255
- * forgets the id and falls back to the annotation, which is the
3256
- * same recovery a 409 on stage or unstage triggers.
3257
- */
3258
- zod.z.object({
3259
- message: zod.z.string(),
3260
- patchGroupPublished: zod.z.literal(true)
3261
3084
  })])
3262
3085
  }), zod.z.object({
3263
3086
  status: zod.z.literal(400),
@@ -3552,16 +3375,7 @@ var Api = {
3552
3375
  limit: onlyOneIntQueryParam.refine(function (arg) {
3553
3376
  return arg > 0 && arg <= JSON_ENTRIES_BATCH_MAX;
3554
3377
  }, "limit must be between 1 and ".concat(JSON_ENTRIES_BATCH_MAX)).optional(),
3555
- apply_patches: onlyOneBooleanQueryParam.optional(),
3556
- /**
3557
- * The caller's own staged work, and nobody else's.
3558
- *
3559
- * The same flag `/sources/~` takes, and for the same reason — a draft
3560
- * page renders both, so a scoped module beside an unscoped
3561
- * `jsonValues` entry showed one person's view and everybody's pending
3562
- * work on the same screen.
3563
- */
3564
- own_patch_groups_only: onlyOneBooleanQueryParam.optional()
3378
+ apply_patches: onlyOneBooleanQueryParam.optional()
3565
3379
  },
3566
3380
  cookies: getSitemapTree._defineProperty({}, VAL_SESSION_COOKIE, zod.z.string().optional())
3567
3381
  },
@@ -4627,13 +4441,11 @@ function newestCommitSha(commits) {
4627
4441
  *
4628
4442
  * The head the server REPORTS (`headPatchId`) when it reports one, and not the
4629
4443
  * last patch it listed. The list — `/applicable/patches`, and `/stat` on top of
4630
- * it — leaves out patches the running deployment already contains, and since
4631
- * patch groups a publish can ship later patches while an earlier one stays
4632
- * pending (another author's change that nets to nothing can never be published
4633
- * at all). The last listed id is then behind the head for good, and the content
4634
- * service accepts a write only on the head, so every save naming it is refused
4635
- * — and a re-sync returns the same list. What fixed it in the Studio before
4636
- * this existed was discarding the other author's change.
4444
+ * it — leaves out patches the running deployment already contains, and a
4445
+ * publish can ship later patches while an earlier one stays pending (a failed
4446
+ * publish holds its changes back). The last listed id is then behind the head
4447
+ * for good, and the content service accepts a write only on the head, so every
4448
+ * save naming it is refused — and a re-sync returns the same list.
4637
4449
  *
4638
4450
  * `null` is a REPORTED empty chain, not "unknown". `undefined` is a server
4639
4451
  * that reports no head (`fs` mode, which ignores the parent anyway, or a
@@ -5038,7 +4850,7 @@ function declaredLocales(snapshot) {
5038
4850
  * these has to resolve all of them, and the two that leaked did so by adding a
5039
4851
  * fix here and not to a condition somewhere else.
5040
4852
  */
5041
- var SCHEMA_SOURCE_FIXES = ["keyof:check-keys", "router:check-route", "locale:check-locale", "record:fill-keys"];
4853
+ var SCHEMA_SOURCE_FIXES = ["keyof:check-keys", "image:check-gallery", "file:check-gallery", "video:check-gallery", "router:check-route", "locale:check-locale", "record:fill-keys"];
5042
4854
 
5043
4855
  /** Whether an error is a marker only the whole project can answer. */
5044
4856
  function isSchemaSourceFixError(error) {
@@ -5049,8 +4861,9 @@ function isSchemaSourceFixError(error) {
5049
4861
  }
5050
4862
 
5051
4863
  /**
5052
- * The record a `keyof:check-keys` error resolves against, or null for any other
5053
- * error — including a malformed marker, which
4864
+ * The record a `keyof:check-keys` error resolves against -- or a gallery
4865
+ * marker's (`image:check-gallery` and the rest): its gallery module -- or null
4866
+ * for any other error, including a malformed marker, which
5054
4867
  * {@link resolveSchemaSourceFixForError} reports and this has nothing to say
5055
4868
  * about.
5056
4869
  *
@@ -5060,6 +4873,12 @@ function isSchemaSourceFixError(error) {
5060
4873
  */
5061
4874
  function keyOfRecordPath(error) {
5062
4875
  var _error$fixes2;
4876
+ // A gallery-backed field's marker reads its gallery the same way: the
4877
+ // record whose keys say whether the field's path is there.
4878
+ var gallery = core.Internal.media.galleryMarkerOf(error);
4879
+ if (gallery !== null) {
4880
+ return gallery.gallery;
4881
+ }
5063
4882
  if (!((_error$fixes2 = error.fixes) !== null && _error$fixes2 !== void 0 ? _error$fixes2 : []).includes("keyof:check-keys")) {
5064
4883
  return null;
5065
4884
  }
@@ -5081,6 +4900,37 @@ function keyOfRecordPath(error) {
5081
4900
  function resolveSchemaSourceFixForError(error, snapshot) {
5082
4901
  var _error$fixes3;
5083
4902
  var fixes = (_error$fixes3 = error.fixes) !== null && _error$fixes3 !== void 0 ? _error$fixes3 : [];
4903
+ var gallery = core.Internal.media.galleryMarkerOf(error);
4904
+ if (gallery !== null) {
4905
+ // The gallery as it is now -- with what a pending change, a save in a
4906
+ // proposal or a content-only publish added -- not as the schema was built.
4907
+ var entries = getKeyOfRecordAt(gallery.gallery, snapshot).source;
4908
+ var has = getSitemapTree._typeof(entries) === "object" && entries !== null && !Array.isArray(entries) && core.Internal.media.galleryKeyOf(gallery.path, function (key) {
4909
+ return key in entries;
4910
+ }) !== null;
4911
+ if (has) {
4912
+ return {
4913
+ status: "resolved"
4914
+ };
4915
+ }
4916
+ return {
4917
+ status: "remaining",
4918
+ error: getSitemapTree._objectSpread2(getSitemapTree._objectSpread2({}, error), {}, {
4919
+ value: gallery.source,
4920
+ fixes: undefined
4921
+ })
4922
+ };
4923
+ }
4924
+ if (fixes.includes("image:check-gallery") || fixes.includes("file:check-gallery") || fixes.includes("video:check-gallery")) {
4925
+ return {
4926
+ status: "remaining",
4927
+ error: getSitemapTree._objectSpread2(getSitemapTree._objectSpread2({}, error), {}, {
4928
+ message: "".concat(error.message, " The gallery could not be read from this error. ").concat(TYPE_ERROR_MESSAGE),
4929
+ typeError: true,
4930
+ fixes: undefined
4931
+ })
4932
+ };
4933
+ }
5084
4934
  if (fixes.includes("keyof:check-keys")) {
5085
4935
  if (!error.value) {
5086
4936
  return {
@@ -6372,6 +6222,9 @@ function isSkippableFixCode(fix) {
6372
6222
  // also took them off the publish gate, so a broken key published unseen.
6373
6223
  return false;
6374
6224
  case "keyof:check-keys":
6225
+ case "image:check-gallery":
6226
+ case "file:check-gallery":
6227
+ case "video:check-gallery":
6375
6228
  case "router:check-route":
6376
6229
  case "locale:check-locale":
6377
6230
  case "record:fill-keys":
@@ -8005,7 +7858,12 @@ var requestStatus = zod.z.union([zod.z.object({
8005
7858
  kind: zod.z.literal("failed"),
8006
7859
  message: zod.z.string(),
8007
7860
  actions: zod.z.array(zod.z["enum"](["try-again", "discard", "re-run-build"])),
8008
- job: zod.z.string()
7861
+ job: zod.z.string(),
7862
+ movedTo: zod.z.object({
7863
+ proposal: zod.z.string(),
7864
+ displayName: zod.z.string(),
7865
+ changes: zod.z.number()
7866
+ }).optional()
8009
7867
  }), zod.z.object({
8010
7868
  kind: zod.z.literal("cancelled")
8011
7869
  }), zod.z.object({
@@ -8088,6 +7946,637 @@ function parse(schema, body, call) {
8088
7946
  return parsed.data;
8089
7947
  }
8090
7948
 
7949
+ /**
7950
+ * This functions returns all the possible schema types of a given module path.
7951
+ */
7952
+ function schemaTypesOfPath(schema, patchPath) {
7953
+ if (!patchPath || patchPath.length === 0) {
7954
+ return new Set([schema.type]);
7955
+ }
7956
+ var branches = new Set();
7957
+ var current = schema;
7958
+ var i = -1;
7959
+ var _iterator = getSitemapTree._createForOfIteratorHelper(patchPath),
7960
+ _step;
7961
+ try {
7962
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
7963
+ var pathPart = _step.value;
7964
+ i++;
7965
+ if (!current) {
7966
+ break;
7967
+ }
7968
+ if (current.type === "array") {
7969
+ current = current.item;
7970
+ } else if (current.type === "record") {
7971
+ current = current.item;
7972
+ } else if (current.type === "object" || current.type === "settings") {
7973
+ current = current.items[pathPart];
7974
+ } else if (current.type === "richtext") {
7975
+ // richtext has internal structure (nodes with indices and tags), allow traversal
7976
+ break;
7977
+ } else if (current.type === "discriminated-union") {
7978
+ var types = current.items;
7979
+ var _iterator2 = getSitemapTree._createForOfIteratorHelper(types),
7980
+ _step2;
7981
+ try {
7982
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
7983
+ var type = _step2.value;
7984
+ var subTypes = schemaTypesOfPath(type, patchPath.slice(i));
7985
+ for (var _i = 0, _Array$from = Array.from(subTypes.values()); _i < _Array$from.length; _i++) {
7986
+ var subType = _Array$from[_i];
7987
+ branches.add(subType);
7988
+ }
7989
+ }
7990
+ } catch (err) {
7991
+ _iterator2.e(err);
7992
+ } finally {
7993
+ _iterator2.f();
7994
+ }
7995
+ return branches;
7996
+ } else if (current.type === "enum" || current.type === "boolean" || current.type === "number" || current.type === "string" || current.type === "date" || current.type === "dateTime" || current.type === "color" || current.type === "code" || current.type === "file" || current.type === "image" || current.type === "video" || current.type === "keyOf" || current.type === "route" || current.type === "locale" || current.type === "literal" ||
7997
+ // Nothing is patchable at a view path: the target module owns its source.
7998
+ current.type === "view") {
7999
+ if (current.type === "image" || current.type === "file") {
8000
+ // allow changes to metadata
8001
+ if (patchPath[i] === "metadata") {
8002
+ break;
8003
+ }
8004
+ }
8005
+ if (current.type === "video") {
8006
+ // A video is edited a property at a time — its poster time, a caption
8007
+ // track's label — so that two editors changing different parts of one
8008
+ // video do not overwrite each other. Everything below it is the video.
8009
+ break;
8010
+ }
8011
+ if (i !== patchPath.length - 1) {
8012
+ throw new Error("Found " + current.type + " (primitive), but path has more parts: " + patchPath.join("/") + " at " + pathPart);
8013
+ }
8014
+ break;
8015
+ } else {
8016
+ var _unreachable = current;
8017
+ var unknownType = _unreachable.type;
8018
+ console.error("Unexecpted resolved schema type: ".concat(unknownType, " in ").concat(patchPath.join("/"), " at ").concat(pathPart));
8019
+ return new Set([unknownType]);
8020
+ }
8021
+ }
8022
+ } catch (err) {
8023
+ _iterator.e(err);
8024
+ } finally {
8025
+ _iterator.f();
8026
+ }
8027
+ if (current) {
8028
+ branches.add(current.type);
8029
+ // A discriminated union's value IS an object, and an enum's IS a string:
8030
+ // a patch op typed for either has to be accepted here too.
8031
+ if (current.type === "discriminated-union") {
8032
+ branches.add("object");
8033
+ } else if (current.type === "enum") {
8034
+ branches.add("string");
8035
+ }
8036
+ }
8037
+ return branches;
8038
+ }
8039
+
8040
+ // Examples: /file/name.val.ts?/path/to/patch
8041
+
8042
+ /**
8043
+ * A patch set is a set of patches that are non-independent of each other. This is useful for example when we want to apply a set of patches atomically.
8044
+ * A patch set is identified by a module file path and a json patch path delimited by a '?' character. We call this the patch set path. Example: /file/name.val.ts?/path/to/patch
8045
+ *
8046
+ *
8047
+ * NOTE: the order of patch sets is always newest first. This is because the UX always shows the newest patches first.
8048
+ */
8049
+
8050
+ /**
8051
+ * Is `inner` strictly nested inside the patch set `outer`?
8052
+ *
8053
+ * A plain `inner.startsWith(outer)` is not enough, and the difference is not
8054
+ * cosmetic. The file part of a key is terminated by the '?' delimiter, but the
8055
+ * segments of the patch path are not terminated by anything, so a raw prefix
8056
+ * test also matches siblings whose *name* merely starts with the same
8057
+ * characters: '/p.val.ts?foobar/title' starts with '/p.val.ts?foo', so removing
8058
+ * record key "foo" and retitling record key "foobar" would be merged into one
8059
+ * patch set.
8060
+ *
8061
+ * That used to only over-group two unrelated changes in the compare view. Now
8062
+ * that patch sets decide what moves between branches as one (whole patch sets,
8063
+ * as `changesToMove` moves them), it means moving one key silently moves its
8064
+ * similarly-named sibling — so the boundary has to be
8065
+ * checked explicitly: '?' when `outer` is a bare module file path, '/' when it
8066
+ * already has a patch path.
8067
+ */
8068
+ function isInsidePatchSetPath(inner, outer) {
8069
+ if (!inner.startsWith(outer)) {
8070
+ return false;
8071
+ }
8072
+ var boundary = inner[outer.length];
8073
+ return outer.includes("?") ? boundary === "/" : boundary === "?";
8074
+ }
8075
+ var PatchSets = /*#__PURE__*/function () {
8076
+ function PatchSets() {
8077
+ _classCallCheck(this, PatchSets);
8078
+ getSitemapTree._defineProperty(this, "insertedPatches", void 0);
8079
+ getSitemapTree._defineProperty(this, "patchSetMetadata", void 0);
8080
+ getSitemapTree._defineProperty(this, "orderedInsertKeys", void 0);
8081
+ this.patchSetMetadata = {};
8082
+ this.orderedInsertKeys = [];
8083
+ this.insertedPatches = new Set();
8084
+ }
8085
+ return _createClass(PatchSets, [{
8086
+ key: "reset",
8087
+ value: function reset() {
8088
+ this.patchSetMetadata = {};
8089
+ this.orderedInsertKeys = [];
8090
+ this.insertedPatches = new Set();
8091
+ }
8092
+ }, {
8093
+ key: "getInsertedPatches",
8094
+ value: function getInsertedPatches() {
8095
+ return this.insertedPatches;
8096
+ }
8097
+ }, {
8098
+ key: "insertPath",
8099
+ value: function insertPath(moduleFilePath, affectsPatchPath, patchId, createdAt, author, opType, schemaTypes, opPath) {
8100
+ var newPatchSetPath = affectsPatchPath.length > 0 ? "".concat(moduleFilePath, "?").concat(affectsPatchPath.join("/")) : moduleFilePath;
8101
+ var pathIndexesThatMustBeMerged = [];
8102
+ // TODO: current implementation is O(n), with isInside it is: O(n x m) - there's room for optimization (Trie?). Just make sure order is maintained and that insert AND then serialize is what we optimize for because the UX will do an insert, then serialize immediately after
8103
+ for (var i = this.orderedInsertKeys.length - 1; i >= 0; i--) {
8104
+ var currentInsertKey = this.orderedInsertKeys[i];
8105
+ if (newPatchSetPath !== currentInsertKey) {
8106
+ if (isInsidePatchSetPath(newPatchSetPath, currentInsertKey)) {
8107
+ // This new patch set is inside an existing patch set...
8108
+ // Use the existing patch set as the new name
8109
+ newPatchSetPath = currentInsertKey;
8110
+ // Move to new patch set to head
8111
+ this.orderedInsertKeys.splice(i, 1);
8112
+ this.orderedInsertKeys.unshift(newPatchSetPath);
8113
+ } else if (isInsidePatchSetPath(currentInsertKey, newPatchSetPath)) {
8114
+ // We found a patch set (with a shorter path) that needs to be merged into this new patch set
8115
+ pathIndexesThatMustBeMerged.push(i);
8116
+ }
8117
+ } else {
8118
+ // This patch set already exists
8119
+ // Move to new patch set to head
8120
+ this.orderedInsertKeys.splice(i, 1);
8121
+ this.orderedInsertKeys.unshift(newPatchSetPath);
8122
+ }
8123
+ }
8124
+
8125
+ // Merge existing patch sets into this new one:
8126
+ for (var _i = 0, _pathIndexesThatMustB = pathIndexesThatMustBeMerged; _i < _pathIndexesThatMustB.length; _i++) {
8127
+ var pathIndexThatMustBeMerged = _pathIndexesThatMustB[_i];
8128
+ var moduleFileOrPatchSetPath = this.orderedInsertKeys[pathIndexThatMustBeMerged];
8129
+
8130
+ // 1) remove the old patch set path from ordered insert key
8131
+ this.orderedInsertKeys.splice(pathIndexThatMustBeMerged, 1);
8132
+
8133
+ // 2) merge metadata into new
8134
+ var existingPatchSetMetadata = this.patchSetMetadata[moduleFileOrPatchSetPath];
8135
+ if (!this.patchSetMetadata[newPatchSetPath]) {
8136
+ this.patchSetMetadata[newPatchSetPath] = {
8137
+ moduleFilePath: moduleFilePath,
8138
+ patchPath: affectsPatchPath,
8139
+ patches: [],
8140
+ authors: [],
8141
+ opTypes: [],
8142
+ schemaTypes: Array.from(schemaTypes),
8143
+ lastUpdated: createdAt,
8144
+ lastUpdatedBy: author
8145
+ };
8146
+ }
8147
+ if (existingPatchSetMetadata) {
8148
+ var _iterator = getSitemapTree._createForOfIteratorHelper(existingPatchSetMetadata.patches.slice()),
8149
+ _step;
8150
+ try {
8151
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
8152
+ var patchMetadata = _step.value;
8153
+ this.patchSetMetadata[newPatchSetPath].patches.unshift(patchMetadata);
8154
+ if (patchMetadata.author !== null) {
8155
+ this.patchSetMetadata[newPatchSetPath].authors.unshift(patchMetadata.author);
8156
+ }
8157
+ this.patchSetMetadata[newPatchSetPath].opTypes.unshift(patchMetadata.opType);
8158
+ }
8159
+ } catch (err) {
8160
+ _iterator.e(err);
8161
+ } finally {
8162
+ _iterator.f();
8163
+ }
8164
+ } else {
8165
+ throw new Error("Could not find patch set metadata or patch metadata for (inserted) patch set path: ".concat(moduleFileOrPatchSetPath));
8166
+ }
8167
+
8168
+ // 3) delete old metadata
8169
+ if (moduleFileOrPatchSetPath !== newPatchSetPath) {
8170
+ delete this.patchSetMetadata[moduleFileOrPatchSetPath];
8171
+ }
8172
+ }
8173
+
8174
+ // Insert new metadata
8175
+ if (!this.patchSetMetadata[newPatchSetPath]) {
8176
+ this.patchSetMetadata[newPatchSetPath] = {
8177
+ moduleFilePath: moduleFilePath,
8178
+ patchPath: affectsPatchPath,
8179
+ patches: [],
8180
+ authors: [],
8181
+ opTypes: [],
8182
+ schemaTypes: schemaTypes,
8183
+ lastUpdated: createdAt,
8184
+ lastUpdatedBy: author
8185
+ };
8186
+ }
8187
+ if (author !== null && !this.patchSetMetadata[newPatchSetPath].authors.includes(author)) {
8188
+ this.patchSetMetadata[newPatchSetPath].authors.unshift(author);
8189
+ }
8190
+ this.patchSetMetadata[newPatchSetPath].lastUpdatedBy = author;
8191
+ this.patchSetMetadata[newPatchSetPath].lastUpdated = createdAt;
8192
+ if (!this.patchSetMetadata[newPatchSetPath].opTypes.includes(opType)) {
8193
+ this.patchSetMetadata[newPatchSetPath].opTypes.unshift(opType);
8194
+ }
8195
+ this.patchSetMetadata[newPatchSetPath].patches.unshift({
8196
+ patchPath: opPath,
8197
+ patchId: patchId,
8198
+ author: author,
8199
+ createdAt: createdAt,
8200
+ opType: opType,
8201
+ schemaTypes: schemaTypes
8202
+ });
8203
+ if (!this.orderedInsertKeys.includes(newPatchSetPath)) {
8204
+ this.orderedInsertKeys.unshift(newPatchSetPath);
8205
+ }
8206
+ }
8207
+
8208
+ /**
8209
+ * Insert a whole patch.
8210
+ *
8211
+ * Takes the patch rather than one op because the de-duplication below is
8212
+ * per-*patch*: a patch is either known to this instance or it is not. When this
8213
+ * took a single op, callers looped and every call after the first hit the
8214
+ * `insertedPatches` guard and returned — so only a patch's first op ever reached
8215
+ * a patch set. A patch that touched two places (a `move`, or a `file` op sitting
8216
+ * before its source op) was therefore mis-grouped, which is invisible in the
8217
+ * compare view but decides what moves as one.
8218
+ */
8219
+ }, {
8220
+ key: "insert",
8221
+ value: function insert(moduleFilePath, schema, patch, patchId, createdAt, author) {
8222
+ if (this.insertedPatches.has(patchId)) {
8223
+ return;
8224
+ }
8225
+ // Marked inserted unconditionally, schema or not. An earlier revision of this
8226
+ // fix only marked it once a schema was available, so that a patch grouped
8227
+ // under the whole-module fallback could be re-inserted later with the real
8228
+ // schema. That is no longer this class's problem: `PatchSetChain` invalidates
8229
+ // on `schema:init` and a rebuild calls `reset()`, which clears this set — so
8230
+ // the fallback is already re-derived from scratch when the schema lands, and a
8231
+ // conditional here would only leave the per-patch de-duplication half-applied.
8232
+ this.insertedPatches.add(patchId);
8233
+ var _iterator2 = getSitemapTree._createForOfIteratorHelper(patch),
8234
+ _step2;
8235
+ try {
8236
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
8237
+ var op = _step2.value;
8238
+ this.insertOp(moduleFilePath, schema, op, patchId, createdAt, author);
8239
+ }
8240
+ } catch (err) {
8241
+ _iterator2.e(err);
8242
+ } finally {
8243
+ _iterator2.f();
8244
+ }
8245
+ }
8246
+ }, {
8247
+ key: "insertOp",
8248
+ value: function insertOp(moduleFilePath, schema, op, patchId, createdAt, author) {
8249
+ if (!schema) {
8250
+ this.insertPath(moduleFilePath, [], patchId, createdAt, author, op.op, [], op.path);
8251
+ return;
8252
+ }
8253
+ if (op.op === "file" || op.op === "test") {
8254
+ return;
8255
+ }
8256
+ try {
8257
+ if (op.op === "replace") {
8258
+ var schemaTypesAtPath = schemaTypesOfPath(schema, op.path);
8259
+ this.insertPath(moduleFilePath, op.path, patchId, createdAt, author, op.op, Array.from(schemaTypesAtPath), op.path);
8260
+ } else if (op.op === "add" || op.op === "remove" || op.op === "move" || op.op === "copy") {
8261
+ // This is the default case, were we, to be sure, take the parent of the path and as the patch set
8262
+ var path = op.path.slice(0, -1);
8263
+ var _schemaTypesAtPath = schemaTypesOfPath(schema, path);
8264
+ if (_schemaTypesAtPath.size === 1 && _schemaTypesAtPath.has("array")) {
8265
+ // for arrays we would need a lot of logic to create a patch set that is not on the entire parent so for now we do just that
8266
+ this.insertPath(moduleFilePath, path, patchId, createdAt, author, op.op, Array.from(_schemaTypesAtPath), op.path);
8267
+ if (op.op === "move" || op.op === "copy") {
8268
+ // `copy` as well as `move`: it does not write the source, but it
8269
+ // READS it, so the value it produces depends on every pending edit
8270
+ // to that source and on the indices around it.
8271
+ var _path = op.from.slice(0, -1);
8272
+ var _schemaTypesAtPath2 = schemaTypesOfPath(schema, _path);
8273
+ this.insertPath(moduleFilePath, _path, patchId, createdAt, author, op.op, Array.from(_schemaTypesAtPath2), op.path);
8274
+ }
8275
+ } else if (_schemaTypesAtPath.size === 1 && (_schemaTypesAtPath.has("record") ||
8276
+ // An OBJECT is keyed too: its keys are named rather than
8277
+ // positional, so neither `add` (create-or-set, `addToNode` in
8278
+ // `@valbuild/core/patch`) nor `remove` (delete the property)
8279
+ // disturbs any sibling. Either way the op affects exactly the key
8280
+ // it names and nothing else, which is the property this branch
8281
+ // needs — unlike an array, where both shift every later index.
8282
+ //
8283
+ // The studio emits `add` rather than `replace` here on purpose, so
8284
+ // that the write survives the key having gone away in the meantime:
8285
+ // see `ImageField`'s alt write and
8286
+ // `ModuleGallery.handleAltTextChange`, which both say so.
8287
+ //
8288
+ // That last one is why this branch exists. `s.imagedir()`
8289
+ // serializes as a record whose ITEM is an object, so editing the
8290
+ // alt text of a gallery entry resolves `object` here, hit the throw
8291
+ // below, and terminated the whole media module into one patch set —
8292
+ // once per keystroke. Moving any one change in that module then
8293
+ // dragged every other change in it along, because changes move a
8294
+ // whole patch set at a time.
8295
+ _schemaTypesAtPath.has("object") ||
8296
+ // A settings SECTION is addressed like a record: its keys are
8297
+ // named, and an `add` at ["theme", "accent"] modifies exactly that
8298
+ // one. Without this it fell to the throw below — every settings
8299
+ // edit terminated the whole module into one patch set, which is why
8300
+ // the publish diff said "Settings" where `settingsChangeLabels` has
8301
+ // a name for the field. Both sections write `add` this way; see
8302
+ // `useWriteAssistantSetting`, which has to create an absent section
8303
+ // in a single op.
8304
+ _schemaTypesAtPath.has("settings"))) {
8305
+ // If we know this is a record, we can be more specific and only insert the path that is being modified
8306
+ var _path2 = op.path;
8307
+ var _schemaTypesAtPath3 = schemaTypesOfPath(schema, _path2);
8308
+ this.insertPath(moduleFilePath, _path2, patchId, createdAt, author, op.op, Array.from(_schemaTypesAtPath3), op.path);
8309
+ if (op.op === "move" || op.op === "copy") {
8310
+ // The SOURCE is classified on its own terms, not the
8311
+ // destination's. A move out of an array item shifts every later
8312
+ // index, so that side has to be the array — exactly as the branch
8313
+ // above does for a move whose destination is an array. Only when
8314
+ // the source parent is keyed too does `op.from` name the whole of
8315
+ // what the op affects.
8316
+ //
8317
+ // `copy` reaches here as well as `move`. It does not write the
8318
+ // source, but the value it produces is read from it, so it depends
8319
+ // on every pending edit to that source just as a move does.
8320
+ // Duplicating a record entry (`useDuplicateRecordEntry`) is
8321
+ // the copy that actually ships, and its source went ungrouped.
8322
+ var fromParent = op.from.slice(0, -1);
8323
+ var fromParentTypes = schemaTypesOfPath(schema, fromParent);
8324
+ // ANY possible array parent widens, rather than only an
8325
+ // unambiguous one: a discriminated union resolves to every
8326
+ // variant's type at once, so a source inside one can be an array
8327
+ // here and an object there. Requiring a single type grouped it at
8328
+ // the item, which is wrong the moment the array variant is the
8329
+ // live one — and the whole array is the safe direction.
8330
+ var isPositionalSource = fromParentTypes.has("array");
8331
+ var _path3 = isPositionalSource ? fromParent : op.from;
8332
+ var _schemaTypesAtPath4 = schemaTypesOfPath(schema, _path3);
8333
+ this.insertPath(moduleFilePath, _path3, patchId, createdAt, author, op.op, Array.from(_schemaTypesAtPath4), op.path);
8334
+ }
8335
+ } else if (_schemaTypesAtPath.size === 1 && !(_schemaTypesAtPath.has("image") || _schemaTypesAtPath.has("file"))) {
8336
+ // What is left is a parent this function cannot isolate a key
8337
+ // inside. Two different reasons end up here, and the message covers
8338
+ // both: a PRIMITIVE cannot hold the key at all, which means the path
8339
+ // no longer fits the schema — a genuinely stale patch; and `richtext`
8340
+ // can, but its children are positional, so isolating one would let a
8341
+ // node insert be moved without the edits whose indices it shifted.
8342
+ // Both keep reporting, and the catch below terminates the module,
8343
+ // which is the conservative thing to do when we cannot say what a
8344
+ // change affects.
8345
+ throw new Error("Cannot isolate a patch set for op: '".concat(op.op, "' \u2014 no addressable key inside schema type: ").concat(_schemaTypesAtPath.values().next().value));
8346
+ } else {
8347
+ // we cannot really know if this is a record or array so the entire module is the patch set
8348
+ this.insertPath(moduleFilePath, path, patchId, createdAt, author, op.op, [schema.type], op.path);
8349
+ }
8350
+ } else {
8351
+ var _unreachable = op;
8352
+ throw new Error("Unreachable op: " + _unreachable);
8353
+ }
8354
+ } catch (e) {
8355
+ // We might be in this situation if the schema changes, etc etc:
8356
+ if (e instanceof Error) {
8357
+ console.error("Could not resolve path while creating patch set", e.message);
8358
+ } else {
8359
+ console.error("Could not resolve path while creating patch set", e);
8360
+ }
8361
+ // "terminate" the entire module file path (i.e. all patches are their own patch set)
8362
+ this.insertPath(moduleFilePath, [], patchId, createdAt, author, op.op, [schema.type], op.path);
8363
+ }
8364
+ }
8365
+ }, {
8366
+ key: "isInserted",
8367
+ value: function isInserted(patchId) {
8368
+ return this.insertedPatches.has(patchId);
8369
+ }
8370
+ }, {
8371
+ key: "serialize",
8372
+ value: function serialize() {
8373
+ var _this = this;
8374
+ return this.orderedInsertKeys.map(function (key) {
8375
+ if (key in _this.patchSetMetadata) {
8376
+ return _this.patchSetMetadata[key];
8377
+ } else {
8378
+ throw new Error("Could not find patch set metadata or patch metadata for (inserted) patch set path: ".concat(key));
8379
+ }
8380
+ });
8381
+ }
8382
+ }]);
8383
+ }();
8384
+
8385
+ /**
8386
+ * Patch sets that share a change become one: a change is never split.
8387
+ *
8388
+ * Shared by the two places that move changes between branches a whole set at
8389
+ * a time -- the merge moving the site's conflicting pending changes
8390
+ * (`changesToMove` in `@valbuild/server`) and Publish selected
8391
+ * ({@link selectionClosure}) -- so that they agree on what "a whole set" is.
8392
+ */
8393
+ function joinSharedChanges(sets) {
8394
+ var groups = [];
8395
+ var _iterator = getSitemapTree._createForOfIteratorHelper(sets),
8396
+ _step;
8397
+ try {
8398
+ var _loop = function _loop() {
8399
+ var set = _step.value;
8400
+ var joined = new Set(set);
8401
+ var _iterator2 = getSitemapTree._createForOfIteratorHelper(groups.filter(function (g) {
8402
+ return set.some(function (id) {
8403
+ return g.has(id);
8404
+ });
8405
+ })),
8406
+ _step2;
8407
+ try {
8408
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
8409
+ var group = _step2.value;
8410
+ var _iterator3 = getSitemapTree._createForOfIteratorHelper(group),
8411
+ _step3;
8412
+ try {
8413
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
8414
+ var id = _step3.value;
8415
+ joined.add(id);
8416
+ }
8417
+ } catch (err) {
8418
+ _iterator3.e(err);
8419
+ } finally {
8420
+ _iterator3.f();
8421
+ }
8422
+ groups.splice(groups.indexOf(group), 1);
8423
+ }
8424
+ } catch (err) {
8425
+ _iterator2.e(err);
8426
+ } finally {
8427
+ _iterator2.f();
8428
+ }
8429
+ groups.push(joined);
8430
+ };
8431
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
8432
+ _loop();
8433
+ }
8434
+ } catch (err) {
8435
+ _iterator.e(err);
8436
+ } finally {
8437
+ _iterator.f();
8438
+ }
8439
+ return groups;
8440
+ }
8441
+
8442
+ /**
8443
+ * What publishing `selected` publishes: every change in every patch set a
8444
+ * selected change is in, sets that share a change joined
8445
+ * (valbuild/val `docs/plans/remove-patch-groups.md`, Phase 4, rule 2).
8446
+ *
8447
+ * A patch set is changes that do not apply without each other, so a selection
8448
+ * of part of one cannot be published on its own. What it pulls in is someone
8449
+ * else's as often as not; the caller says whose, before anything moves.
8450
+ *
8451
+ * In the order of `patchIds` -- the chain's -- with nothing that is not in it.
8452
+ */
8453
+ function selectionClosure(patchSets, selected, patchIds) {
8454
+ var wanted = new Set(selected);
8455
+ var closed = new Set();
8456
+ var _iterator4 = getSitemapTree._createForOfIteratorHelper(joinSharedChanges(patchSets.map(function (set) {
8457
+ return set.patches.map(function (patch) {
8458
+ return patch.patchId;
8459
+ });
8460
+ }))),
8461
+ _step4;
8462
+ try {
8463
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
8464
+ var group = _step4.value;
8465
+ if (_toConsumableArray(group).some(function (id) {
8466
+ return wanted.has(id);
8467
+ })) {
8468
+ var _iterator6 = getSitemapTree._createForOfIteratorHelper(group),
8469
+ _step6;
8470
+ try {
8471
+ for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
8472
+ var id = _step6.value;
8473
+ closed.add(id);
8474
+ }
8475
+ } catch (err) {
8476
+ _iterator6.e(err);
8477
+ } finally {
8478
+ _iterator6.f();
8479
+ }
8480
+ }
8481
+ }
8482
+ // A selected change no patch set knows of is its own set.
8483
+ } catch (err) {
8484
+ _iterator4.e(err);
8485
+ } finally {
8486
+ _iterator4.f();
8487
+ }
8488
+ var _iterator5 = getSitemapTree._createForOfIteratorHelper(wanted),
8489
+ _step5;
8490
+ try {
8491
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
8492
+ var _id = _step5.value;
8493
+ closed.add(_id);
8494
+ }
8495
+ } catch (err) {
8496
+ _iterator5.e(err);
8497
+ } finally {
8498
+ _iterator5.f();
8499
+ }
8500
+ return patchIds.filter(function (id) {
8501
+ return closed.has(id);
8502
+ });
8503
+ }
8504
+
8505
+ /**
8506
+ * Update from the site, for a proposal with code (valbuild/home
8507
+ * `docs/hosted-publishing.md` step 8.2, slice 1b): what content hands over,
8508
+ * what the builder tab makes of it, and what Val's server reads -- one
8509
+ * definition of each for the three.
8510
+ */
8511
+
8512
+ /** Content a schema does not allow, by path in the module. */
8513
+
8514
+ /** Content's `merge-trees` answer. */
8515
+
8516
+ /** Which tree: where the proposal started, the site, or the proposal. */
8517
+
8518
+ /** A module, as evaluating its tree in the tab gave it. */
8519
+
8520
+ /** A side's files: the site's tree, the base's changes over it, the proposal's over that. */
8521
+ function sideFiles(trees, side) {
8522
+ var layers = side === "site" ? [] : side === "base" ? [trees.trees.base] : [trees.trees.base, trees.trees.proposal];
8523
+ var files = getSitemapTree._objectSpread2({}, trees.trees.site);
8524
+ for (var _i = 0, _layers = layers; _i < _layers.length; _i++) {
8525
+ var layer = _layers[_i];
8526
+ for (var _i2 = 0, _Object$entries = Object.entries(layer); _i2 < _Object$entries.length; _i2++) {
8527
+ var _Object$entries$_i = _slicedToArray(_Object$entries[_i2], 2),
8528
+ path = _Object$entries$_i[0],
8529
+ text = _Object$entries$_i[1];
8530
+ if (text === null) delete files[path];else files[path] = text;
8531
+ }
8532
+ }
8533
+ return files;
8534
+ }
8535
+
8536
+ /** A side's Source of a module, from content's index; `null` when it has none. */
8537
+ function sideSource(trees, side, moduleFilePath) {
8538
+ var _trees$modules$module, _trees$modules$module2, _trees$sources$sha;
8539
+ var sha = (_trees$modules$module = (_trees$modules$module2 = trees.modules[moduleFilePath]) === null || _trees$modules$module2 === void 0 ? void 0 : _trees$modules$module2[side]) !== null && _trees$modules$module !== void 0 ? _trees$modules$module : null;
8540
+ return sha === null ? null : (_trees$sources$sha = trees.sources[sha]) !== null && _trees$sources$sha !== void 0 ? _trees$sources$sha : null;
8541
+ }
8542
+
8543
+ /**
8544
+ * What a schema reports about this content, without the errors the server
8545
+ * fixes on its own (those with `fixes`): run on the schema instance that
8546
+ * evaluating the module made, so custom rules take part.
8547
+ */
8548
+ function schemaErrorsOf(schema, moduleFilePath, source) {
8549
+ var res = schema["executeValidate"](moduleFilePath, source);
8550
+ if (res === false) return [];
8551
+ var errors = [];
8552
+ for (var _i3 = 0, _Object$entries2 = Object.entries(res); _i3 < _Object$entries2.length; _i3++) {
8553
+ var _Object$entries2$_i = _slicedToArray(_Object$entries2[_i3], 2),
8554
+ sourcePath = _Object$entries2$_i[0],
8555
+ list = _Object$entries2$_i[1];
8556
+ var _Internal$splitModule = core.Internal.splitModuleFilePathAndModulePath(sourcePath),
8557
+ _Internal$splitModule2 = _slicedToArray(_Internal$splitModule, 2),
8558
+ modulePath = _Internal$splitModule2[1];
8559
+ var path = modulePath ? core.Internal.createPatchPath(modulePath) : [];
8560
+ var _iterator = getSitemapTree._createForOfIteratorHelper(list),
8561
+ _step;
8562
+ try {
8563
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
8564
+ var error = _step.value;
8565
+ if (error.fixes && error.fixes.length > 0) continue;
8566
+ errors.push({
8567
+ path: path,
8568
+ message: error.message
8569
+ });
8570
+ }
8571
+ } catch (err) {
8572
+ _iterator.e(err);
8573
+ } finally {
8574
+ _iterator.f();
8575
+ }
8576
+ }
8577
+ return errors;
8578
+ }
8579
+
8091
8580
  exports.DEFAULT_CANVAS_SELECTION = getSitemapTree.DEFAULT_CANVAS_SELECTION;
8092
8581
  exports.DEFAULT_CANVAS_SELECTION_SOFT = getSitemapTree.DEFAULT_CANVAS_SELECTION_SOFT;
8093
8582
  exports.VAL_AI_SESSION_STORAGE_KEY = getSitemapTree.VAL_AI_SESSION_STORAGE_KEY;
@@ -8131,9 +8620,9 @@ exports.NEVER_ENCODED = NEVER_ENCODED;
8131
8620
  exports.ParentRef = ParentRef$1;
8132
8621
  exports.Patch = Patch;
8133
8622
  exports.PatchBlock = PatchBlock;
8134
- exports.PatchGroup = PatchGroup;
8135
8623
  exports.PatchId = PatchId;
8136
8624
  exports.PatchJSON = PatchJSON;
8625
+ exports.PatchSets = PatchSets;
8137
8626
  exports.PlanRefusalError = PlanRefusalError;
8138
8627
  exports.PublishProtocolError = PublishProtocolError;
8139
8628
  exports.RAMP_STEPS = RAMP_STEPS;
@@ -8218,10 +8707,12 @@ exports.getRoutesOf = getRoutesOf;
8218
8707
  exports.getRoutesWithModulePaths = getRoutesWithModulePaths;
8219
8708
  exports.getSourceAt = getSourceAt;
8220
8709
  exports.indexModule = indexModule;
8710
+ exports.isInsidePatchSetPath = isInsidePatchSetPath;
8221
8711
  exports.isMimeTypeAccepted = isMimeTypeAccepted;
8222
8712
  exports.isRemoteSchema = isRemoteSchema;
8223
8713
  exports.isSchemaSourceFixError = isSchemaSourceFixError;
8224
8714
  exports.isSkippedSource = isSkippedSource;
8715
+ exports.joinSharedChanges = joinSharedChanges;
8225
8716
  exports.keyOfRecordPath = keyOfRecordPath;
8226
8717
  exports.levenshtein = levenshtein;
8227
8718
  exports.localeAt = localeAt;
@@ -8257,6 +8748,11 @@ exports.resolveSerializedSchemaAtPath = resolveSerializedSchemaAtPath;
8257
8748
  exports.richTextImageSchema = richTextImageSchema;
8258
8749
  exports.richTextToRemirror = richTextToRemirror;
8259
8750
  exports.safeParsePatch = safeParsePatch;
8751
+ exports.schemaErrorsOf = schemaErrorsOf;
8752
+ exports.schemaTypesOfPath = schemaTypesOfPath;
8753
+ exports.selectionClosure = selectionClosure;
8754
+ exports.sideFiles = sideFiles;
8755
+ exports.sideSource = sideSource;
8260
8756
  exports.sourcePathOfChild = sourcePathOfChild;
8261
8757
  exports.themeCustomProperties = themeCustomProperties;
8262
8758
  exports.toolNames = toolNames;