@valbuild/shared 0.145.1 → 0.146.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.
@@ -5,13 +5,11 @@ import type { PatchId } from "@valbuild/core";
5
5
  *
6
6
  * The head the server REPORTS (`headPatchId`) when it reports one, and not the
7
7
  * last patch it listed. The list — `/applicable/patches`, and `/stat` on top of
8
- * it — leaves out patches the running deployment already contains, and since
9
- * patch groups a publish can ship later patches while an earlier one stays
10
- * pending (another author's change that nets to nothing can never be published
11
- * at all). The last listed id is then behind the head for good, and the content
12
- * service accepts a write only on the head, so every save naming it is refused
13
- * — and a re-sync returns the same list. What fixed it in the Studio before
14
- * this existed was discarding the other author's change.
8
+ * it — leaves out patches the running deployment already contains, and a
9
+ * publish can ship later patches while an earlier one stays pending (a failed
10
+ * publish holds its changes back). The last listed id is then behind the head
11
+ * for good, and the content service accepts a write only on the head, so every
12
+ * save naming it is refused — and a re-sync returns the same list.
15
13
  *
16
14
  * `null` is a REPORTED empty chain, not "unknown". `undefined` is a server
17
15
  * that reports no head (`fs` mode, which ignores the parent anyway, or a
@@ -37,3 +37,6 @@ export * from "./search/sourcePath.js";
37
37
  export * from "./theme/accentRamp.js";
38
38
  export * from "./publish/contentApi.js";
39
39
  export * from "./publish/protocol.js";
40
+ export * from "./patchSets/PatchSets.js";
41
+ export * from "./patchSets/schemaTypesOfPath.js";
42
+ export * from "./proposalMerge/trees.js";
@@ -0,0 +1,75 @@
1
+ import { ModuleFilePath, PatchId, SerializedSchema } from "@valbuild/core";
2
+ import { Operation } from "@valbuild/core/patch";
3
+ type AuthorId = string;
4
+ type IsoDateString = string;
5
+ export type PatchMetadata = {
6
+ patchId: PatchId;
7
+ patchPath: PatchPath;
8
+ opType: Operation["op"];
9
+ schemaTypes: Array<SerializedSchema["type"]>;
10
+ author: AuthorId | null;
11
+ createdAt: IsoDateString;
12
+ };
13
+ export type PatchSetMetadata = {
14
+ moduleFilePath: ModuleFilePath;
15
+ patchPath: PatchPath;
16
+ patches: PatchMetadata[];
17
+ authors: AuthorId[];
18
+ opTypes: Operation["op"][];
19
+ schemaTypes: Array<SerializedSchema["type"]>;
20
+ lastUpdated: IsoDateString;
21
+ lastUpdatedBy: AuthorId | null;
22
+ };
23
+ /**
24
+ * 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.
25
+ * 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
26
+ *
27
+ *
28
+ * NOTE: the order of patch sets is always newest first. This is because the UX always shows the newest patches first.
29
+ */
30
+ export type SerializedPatchSet = PatchSetMetadata[];
31
+ type PatchPath = string[];
32
+ /**
33
+ * Is `inner` strictly nested inside the patch set `outer`?
34
+ *
35
+ * A plain `inner.startsWith(outer)` is not enough, and the difference is not
36
+ * cosmetic. The file part of a key is terminated by the '?' delimiter, but the
37
+ * segments of the patch path are not terminated by anything, so a raw prefix
38
+ * test also matches siblings whose *name* merely starts with the same
39
+ * characters: '/p.val.ts?foobar/title' starts with '/p.val.ts?foo', so removing
40
+ * record key "foo" and retitling record key "foobar" would be merged into one
41
+ * patch set.
42
+ *
43
+ * That used to only over-group two unrelated changes in the compare view. Now
44
+ * that patch sets decide what moves between branches as one (whole patch sets,
45
+ * as `changesToMove` moves them), it means moving one key silently moves its
46
+ * similarly-named sibling — so the boundary has to be
47
+ * checked explicitly: '?' when `outer` is a bare module file path, '/' when it
48
+ * already has a patch path.
49
+ */
50
+ export declare function isInsidePatchSetPath(inner: string, outer: string): boolean;
51
+ export declare class PatchSets {
52
+ private insertedPatches;
53
+ private patchSetMetadata;
54
+ private orderedInsertKeys;
55
+ constructor();
56
+ reset(): void;
57
+ getInsertedPatches(): Set<PatchId>;
58
+ private insertPath;
59
+ /**
60
+ * Insert a whole patch.
61
+ *
62
+ * Takes the patch rather than one op because the de-duplication below is
63
+ * per-*patch*: a patch is either known to this instance or it is not. When this
64
+ * took a single op, callers looped and every call after the first hit the
65
+ * `insertedPatches` guard and returned — so only a patch's first op ever reached
66
+ * a patch set. A patch that touched two places (a `move`, or a `file` op sitting
67
+ * before its source op) was therefore mis-grouped, which is invisible in the
68
+ * compare view but decides what moves as one.
69
+ */
70
+ insert(moduleFilePath: ModuleFilePath, schema: SerializedSchema | undefined, patch: Operation[], patchId: PatchId, createdAt: IsoDateString, author: AuthorId | null): void;
71
+ private insertOp;
72
+ isInserted(patchId: PatchId): boolean;
73
+ serialize(): SerializedPatchSet;
74
+ }
75
+ export {};
@@ -0,0 +1,5 @@
1
+ import { SerializedSchema } from "@valbuild/core";
2
+ /**
3
+ * This functions returns all the possible schema types of a given module path.
4
+ */
5
+ export declare function schemaTypesOfPath(schema: SerializedSchema, patchPath?: string[]): Set<SerializedSchema["type"]>;
@@ -0,0 +1,56 @@
1
+ import type { ModuleFilePath, Schema, SelectorSource, SerializedSchema } from "@valbuild/core";
2
+ import type { JSONValue } from "@valbuild/core/patch";
3
+ /**
4
+ * Update from the site, for a proposal with code (valbuild/home
5
+ * `docs/hosted-publishing.md` step 8.2, slice 1b): what content hands over,
6
+ * what the builder tab makes of it, and what Val's server reads -- one
7
+ * definition of each for the three.
8
+ */
9
+ /** Content a schema does not allow, by path in the module. */
10
+ export type SchemaError = {
11
+ path: string[];
12
+ message: string;
13
+ };
14
+ /** Content's `merge-trees` answer. */
15
+ export type MergeTrees = {
16
+ base: string | null;
17
+ siteHead: string | null;
18
+ proposalHead: string | null;
19
+ upToDate: boolean;
20
+ builds: {
21
+ base: string;
22
+ site: string;
23
+ };
24
+ trees: {
25
+ site: Record<string, string>;
26
+ base: Record<string, string | null>;
27
+ proposal: Record<string, string | null>;
28
+ };
29
+ modules: Record<string, {
30
+ base: string | null;
31
+ site: string | null;
32
+ proposal: string | null;
33
+ }>;
34
+ sources: Record<string, JSONValue>;
35
+ };
36
+ /** Which tree: where the proposal started, the site, or the proposal. */
37
+ export type Side = "base" | "site" | "proposal";
38
+ /** A module, as evaluating its tree in the tab gave it. */
39
+ export type EvaluatedModule = {
40
+ schema: SerializedSchema;
41
+ /** The content its file's text holds. */
42
+ written: JSONValue;
43
+ /** The errors of the module's Source under this schema. */
44
+ errors: SchemaError[];
45
+ };
46
+ export type Evaluated = Record<ModuleFilePath, EvaluatedModule>;
47
+ /** A side's files: the site's tree, the base's changes over it, the proposal's over that. */
48
+ export declare function sideFiles(trees: MergeTrees, side: Side): Record<string, string>;
49
+ /** A side's Source of a module, from content's index; `null` when it has none. */
50
+ export declare function sideSource(trees: MergeTrees, side: Side, moduleFilePath: ModuleFilePath): JSONValue | null;
51
+ /**
52
+ * What a schema reports about this content, without the errors the server
53
+ * fixes on its own (those with `fixes`): run on the schema instance that
54
+ * evaluating the module made, so custom rules take part.
55
+ */
56
+ export declare function schemaErrorsOf(schema: Schema<SelectorSource>, moduleFilePath: ModuleFilePath, source: JSONValue | undefined): SchemaError[];
@@ -383,10 +383,6 @@ export type ContentPublishApi = {
383
383
  };
384
384
  "/publish-jobs/:jobId/discard": {
385
385
  POST: {
386
- /** The forward closure, as `DELETE /patches` takes it. */
387
- body: {
388
- unstagePatchIds?: string[];
389
- };
390
386
  /** Changes it could not discard: another job in flight holds them. */
391
387
  res: {
392
388
  stillHeld: string[];
@@ -6,14 +6,16 @@
6
6
  * byte-identical each time, because the query and the session are fixed for the
7
7
  * length of a request. This is what makes the second and third call free.
8
8
  *
9
- * ## Why the scope is the REQUEST and can never be wider
10
- *
11
- * The memoised response is `own_patch_groups_only: true` — it contains the
12
- * CALLER'S OWN unpublished work, resolved from their session cookie. A memo
13
- * that outlived the request would serve one author's staged edits to the next
14
- * visitor, which is the exact failure `own_patch_groups_only` exists to
15
- * prevent, and it would be invisible: the draft would simply render somebody
16
- * else's half-finished sentence.
9
+ * ## Why the scope is the REQUEST
10
+ *
11
+ * The memoised response is a DRAFT -- unpublished work, read with the caller's
12
+ * session cookie. Every caller allowed to read drafts gets the same draft (patch
13
+ * groups, which made it per author, are gone), but whether a caller is allowed
14
+ * at all is decided per session. A memo that outlived the request would hand
15
+ * the draft to the next visitor without asking, and it would be invisible: the
16
+ * page would simply render somebody's half-finished sentence. Sharing it across
17
+ * requests is possible now, keyed on the chain's head, and would be its own
18
+ * measured change.
17
19
  *
18
20
  * So this module holds NO state of its own. The caller supplies the box, and
19
21
  * the box has to come from something the framework already scopes to the
@@ -1,7 +1,7 @@
1
1
  import { Json } from "@valbuild/core";
2
- export declare const VAL_SESSION_COOKIE: "val_session";
3
- export declare const VAL_STATE_COOKIE: "val_state";
4
- export declare const VAL_ENABLE_COOKIE_NAME: "val_enable";
2
+ export declare const VAL_SESSION_COOKIE: any;
3
+ export declare const VAL_STATE_COOKIE: any;
4
+ export declare const VAL_ENABLE_COOKIE_NAME: any;
5
5
  export type VAL_SESSION_COOKIE = typeof VAL_SESSION_COOKIE;
6
6
  export type VAL_STATE_COOKIE = typeof VAL_STATE_COOKIE;
7
7
  export type VAL_ENABLE_COOKIE_NAME = typeof VAL_ENABLE_COOKIE_NAME;
@@ -65,8 +65,8 @@ export declare const BinaryFileRef: z.ZodObject<{
65
65
  export type BinaryFileRef = z.infer<typeof BinaryFileRef>;
66
66
  export declare const HistoricalModule: z.ZodObject<{
67
67
  source: z.ZodNullable<z.ZodType<JSONValueT, unknown, z.core.$ZodTypeInternals<JSONValueT, unknown>>>;
68
- schema: z.ZodNullable<z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>>;
69
- patchIds: z.ZodArray<z.ZodString & z.ZodType<import("@valbuild/core").PatchId, string, z.core.$ZodTypeInternals<import("@valbuild/core").PatchId, string>>>;
68
+ schema: z.ZodNullable<z.ZodType<SerializedSchemaT, unknown, z.core.$ZodTypeInternals<SerializedSchemaT, unknown>>>;
69
+ patchIds: z.ZodArray<z.ZodString & z.ZodType<PatchIdT, string, z.core.$ZodTypeInternals<PatchIdT, string>>>;
70
70
  changedPaths: z.ZodArray<z.ZodString>;
71
71
  failures: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
72
72
  kind: z.ZodLiteral<"commit-not-found">;
@@ -110,8 +110,8 @@ export declare const HistoricalPatchSet: z.ZodObject<{
110
110
  }, z.core.$strip>;
111
111
  modules: z.ZodRecord<z.ZodString, z.ZodObject<{
112
112
  source: z.ZodNullable<z.ZodType<JSONValueT, unknown, z.core.$ZodTypeInternals<JSONValueT, unknown>>>;
113
- schema: z.ZodNullable<z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>>;
114
- patchIds: z.ZodArray<z.ZodString & z.ZodType<import("@valbuild/core").PatchId, string, z.core.$ZodTypeInternals<import("@valbuild/core").PatchId, string>>>;
113
+ schema: z.ZodNullable<z.ZodType<SerializedSchemaT, unknown, z.core.$ZodTypeInternals<SerializedSchemaT, unknown>>>;
114
+ patchIds: z.ZodArray<z.ZodString & z.ZodType<PatchIdT, string, z.core.$ZodTypeInternals<PatchIdT, string>>>;
115
115
  changedPaths: z.ZodArray<z.ZodString>;
116
116
  failures: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
117
117
  kind: z.ZodLiteral<"commit-not-found">;
@@ -139,7 +139,7 @@ export declare const HistoricalPatchSet: z.ZodObject<{
139
139
  }, z.core.$strip>], "kind">>;
140
140
  }, z.core.$strip>>;
141
141
  patches: z.ZodArray<z.ZodObject<{
142
- patchId: z.ZodString & z.ZodType<import("@valbuild/core").PatchId, string, z.core.$ZodTypeInternals<import("@valbuild/core").PatchId, string>>;
142
+ patchId: z.ZodString & z.ZodType<PatchIdT, string, z.core.$ZodTypeInternals<PatchIdT, string>>;
143
143
  moduleFilePath: z.ZodString;
144
144
  patch: z.ZodUnknown;
145
145
  authorId: z.ZodNullable<z.ZodString>;
@@ -1,5 +1,4 @@
1
1
  import type { JSONValue as JSONValueT, PatchJSON as PatchJSONT, PatchBlock as PatchBlockT, ParentRef as ParentRefT } from "@valbuild/core/patch";
2
- import type { PatchId as PatchIdT } from "@valbuild/core";
3
2
  import { z } from "zod";
4
3
  /**
5
4
  * JSON, recursively — the shape of a Source over the wire.
@@ -83,7 +83,7 @@ export declare const SerializedSettingsSchema: z.ZodType<SerializedSettingsSchem
83
83
  */
84
84
  export declare const SerializedValViewSchema: z.ZodObject<{
85
85
  type: z.ZodLiteral<"view">;
86
- moduleFilePath: z.ZodType<import("@valbuild/core").ModuleFilePath, unknown, z.core.$ZodTypeInternals<import("@valbuild/core").ModuleFilePath, unknown>>;
86
+ moduleFilePath: z.ZodType<ModuleFilePathT, unknown, z.core.$ZodTypeInternals<ModuleFilePathT, unknown>>;
87
87
  opt: z.ZodLiteral<false>;
88
88
  readonly: z.ZodOptional<z.ZodBoolean>;
89
89
  hidden: z.ZodOptional<z.ZodBoolean>;