@valbuild/server 0.145.1 → 0.146.1
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.
- package/CHANGELOG.md +31 -0
- package/dist/declarations/src/ValOps.d.ts +11 -92
- package/dist/declarations/src/ValOpsFS.d.ts +2 -2
- package/dist/declarations/src/ValOpsHttp.d.ts +32 -184
- package/dist/declarations/src/ValOpsMemory.d.ts +2 -2
- package/dist/declarations/src/ValServer.d.ts +3 -120
- package/dist/valbuild-server.cjs.dev.js +1595 -1698
- package/dist/valbuild-server.cjs.prod.js +1595 -1698
- package/dist/valbuild-server.esm.js +1598 -1701
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# @valbuild/server
|
|
2
2
|
|
|
3
|
+
## 0.146.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [[`2db91a5`](https://github.com/valbuild/val/commit/2db91a5d2a280727eb2b1064e853a03be283289f)]:
|
|
8
|
+
- @valbuild/ui@0.146.1
|
|
9
|
+
|
|
10
|
+
## 0.146.0
|
|
11
|
+
|
|
12
|
+
### Minor Changes
|
|
13
|
+
|
|
14
|
+
- [#861](https://github.com/valbuild/val/pull/861) [`89b0fa8`](https://github.com/valbuild/val/commit/89b0fa82a3ae0d8ccd5d0282c1034f1f40cc4e26) Thanks [@freekh](https://github.com/freekh)! - Proposals can be brought up to date with the site, like a pull request.
|
|
15
|
+
|
|
16
|
+
- **Update from the site** merges what was published on the site since a proposal started into the proposal, field by field. Where the site and the proposal changed the same field, the Publish dialog shows both and asks which to keep.
|
|
17
|
+
- **With code:** a proposal that changes code, or a site whose code changed since the proposal started, is updated too. The Studio runs each version's schemas in the tab, and the merged code is built before it can be published.
|
|
18
|
+
- Publish offers **Update from the site** whenever the site has moved under the proposal.
|
|
19
|
+
|
|
20
|
+
- [#864](https://github.com/valbuild/val/pull/864) [`f0c7d27`](https://github.com/valbuild/val/commit/f0c7d27846c21f81ff6ce79c579185150bc0d063) Thanks [@freekh](https://github.com/freekh)! - Patch groups are removed. **Publish** now publishes every pending change, and
|
|
21
|
+
the Review list shows everyone's pending changes in one list. To keep changes
|
|
22
|
+
out of a publish, move them to a proposal.
|
|
23
|
+
|
|
24
|
+
Draft reads no longer send `own_patch_groups_only`, `patch_id` or
|
|
25
|
+
`include_patch_groups`, and the server no longer has the patch-group routes or
|
|
26
|
+
fields. A content host that still sends them is fine: they are ignored.
|
|
27
|
+
|
|
28
|
+
### Patch Changes
|
|
29
|
+
|
|
30
|
+
- Updated dependencies [[`89b0fa8`](https://github.com/valbuild/val/commit/89b0fa82a3ae0d8ccd5d0282c1034f1f40cc4e26), [`f0c7d27`](https://github.com/valbuild/val/commit/f0c7d27846c21f81ff6ce79c579185150bc0d063)]:
|
|
31
|
+
- @valbuild/ui@0.146.0
|
|
32
|
+
- @valbuild/shared@0.146.0
|
|
33
|
+
|
|
3
34
|
## 0.145.1
|
|
4
35
|
|
|
5
36
|
### Patch Changes
|
|
@@ -2,7 +2,7 @@ import { MediaSource, FileMetadata, FileSource, ImageMetadata, ModuleFilePath, P
|
|
|
2
2
|
import { result } from "@valbuild/core/fp";
|
|
3
3
|
import { JSONValue, ParentRef, Patch, PatchError } from "@valbuild/core/patch";
|
|
4
4
|
import type { HistoryError } from "./history/HistoryError.js";
|
|
5
|
-
import type {
|
|
5
|
+
import type { PlanRefusalErrorT } from "@valbuild/shared/internal";
|
|
6
6
|
import type { AffectedFile, StoredModuleVersion, CommitPage, CommitPatch, HistoricalCommit } from "./history/types.js";
|
|
7
7
|
import { ValSyntaxError, ValSyntaxErrorTree } from "./patch/ts/syntax.js";
|
|
8
8
|
import { ParentPatchId } from "@valbuild/core";
|
|
@@ -280,7 +280,6 @@ export declare abstract class ValOps {
|
|
|
280
280
|
/** Passed straight through — see {@link getJsonEntries}. */
|
|
281
281
|
opts?: {
|
|
282
282
|
applyPatches?: boolean;
|
|
283
|
-
patchIds?: PatchId[];
|
|
284
283
|
}): Promise<{
|
|
285
284
|
status: "success";
|
|
286
285
|
content: JSONValue | null;
|
|
@@ -319,15 +318,6 @@ export declare abstract class ValOps {
|
|
|
319
318
|
limit: number;
|
|
320
319
|
}, opts?: {
|
|
321
320
|
applyPatches?: boolean;
|
|
322
|
-
/**
|
|
323
|
-
* Only these pending patches, or every one when `undefined`.
|
|
324
|
-
*
|
|
325
|
-
* A draft render is scoped to the caller's own groups, and a page renders
|
|
326
|
-
* `jsonValues` entries beside module content — so without this the two
|
|
327
|
-
* halves of one page disagreed about whose unpublished work they showed.
|
|
328
|
-
* `undefined` is what every other caller passes and must keep getting.
|
|
329
|
-
*/
|
|
330
|
-
patchIds?: PatchId[];
|
|
331
321
|
}): Promise<{
|
|
332
322
|
status: "success";
|
|
333
323
|
entries: {
|
|
@@ -430,25 +420,10 @@ export declare abstract class ValOps {
|
|
|
430
420
|
readProjectFile(path: string): Promise<WithGenericError<{
|
|
431
421
|
data: string;
|
|
432
422
|
}>>;
|
|
433
|
-
createPatch(path: ModuleFilePath, patch: Patch, patchId: PatchId, parentRef: ParentRef, sessionId: string | null, authorId: AuthorId | null
|
|
434
|
-
/**
|
|
435
|
-
* Which patch group this patch joins, recorded in the SAME request.
|
|
436
|
-
*
|
|
437
|
-
* Atomic on purpose. The content API runs every refusal before its insert,
|
|
438
|
-
* so an invalid closure is a 400 with nothing written. Recording membership
|
|
439
|
-
* in a second call would let a patch exist outside its author's group if
|
|
440
|
-
* that call failed — and a patch outside your own group is one you cannot
|
|
441
|
-
* publish until a repair puts it back.
|
|
442
|
-
*
|
|
443
|
-
* Optional: `fs` mode has no groups, and a client that predates them sends
|
|
444
|
-
* nothing.
|
|
445
|
-
*/
|
|
446
|
-
patchGroup?: PatchGroupMembership): Promise<result.Result<{
|
|
423
|
+
createPatch(path: ModuleFilePath, patch: Patch, patchId: PatchId, parentRef: ParentRef, sessionId: string | null, authorId: AuthorId | null): Promise<result.Result<{
|
|
447
424
|
error?: undefined;
|
|
448
425
|
patchId: PatchId;
|
|
449
426
|
createdAt: string;
|
|
450
|
-
/** See {@link SaveSourceFilePatchResult} — absent where there are no groups. */
|
|
451
|
-
patchGroupId?: string;
|
|
452
427
|
/** See {@link SaveSourceFilePatchResult}. */
|
|
453
428
|
headVersion?: number;
|
|
454
429
|
}, {
|
|
@@ -589,8 +564,8 @@ export declare abstract class ValOps {
|
|
|
589
564
|
* Almost everything the routes branch on comes from this one fact, which is
|
|
590
565
|
* why it is a named property rather than an `instanceof`. If this server owns
|
|
591
566
|
* the store then there is no content service to authenticate to (so an absent
|
|
592
|
-
* or unverifiable session is anonymous rather than a 401), no
|
|
593
|
-
*
|
|
567
|
+
* or unverifiable session is anonymous rather than a 401), no deployments to
|
|
568
|
+
* report, and a
|
|
594
569
|
* "publish" writes what the host does with it rather than pushing a commit. If
|
|
595
570
|
* it does not, every one of those is the content service's and this server is
|
|
596
571
|
* relaying.
|
|
@@ -628,7 +603,7 @@ export declare abstract class ValOps {
|
|
|
628
603
|
patchIds?: PatchId[];
|
|
629
604
|
excludePatchOps: ExcludePatchOps;
|
|
630
605
|
}): Promise<ExcludePatchOps extends true ? OrderedPatchesMetadata : OrderedPatches>;
|
|
631
|
-
protected abstract saveSourceFilePatch(path: ModuleFilePath, patch: Patch, patchId: PatchId, parentRef: ParentRef | null, authorId: AuthorId | null, sessionId: string | null
|
|
606
|
+
protected abstract saveSourceFilePatch(path: ModuleFilePath, patch: Patch, patchId: PatchId, parentRef: ParentRef | null, authorId: AuthorId | null, sessionId: string | null): Promise<SaveSourceFilePatchResult>;
|
|
632
607
|
protected abstract getSourceFile(path: string): Promise<WithGenericError<{
|
|
633
608
|
data: string;
|
|
634
609
|
}>>;
|
|
@@ -748,37 +723,8 @@ export type GenericErrorMessage = {
|
|
|
748
723
|
message: string;
|
|
749
724
|
details?: unknown;
|
|
750
725
|
};
|
|
751
|
-
/**
|
|
752
|
-
* The patch group a newly created patch joins.
|
|
753
|
-
*
|
|
754
|
-
* `withPatchIds` is the CLOSURE the client computed — the patches that share
|
|
755
|
-
* a patch set with this one and must move with it. It is not derived here and
|
|
756
|
-
* must not be: the closure needs the content schema, and the service that
|
|
757
|
-
* stores groups does not have it. One implementation of that rule, on the side
|
|
758
|
-
* that can actually compute it.
|
|
759
|
-
*
|
|
760
|
-
* Membership rows are stamped with `coreVersion` on the content side, the same
|
|
761
|
-
* stamp the patch row itself carries, so which client wrote a row stays legible
|
|
762
|
-
* after the fact.
|
|
763
|
-
*/
|
|
764
|
-
export type PatchGroupMembership = {
|
|
765
|
-
/**
|
|
766
|
-
* Absent means "the author's open group, created if absent" — the content API
|
|
767
|
-
* resolves it. The client does not hold an id across publishes, because a
|
|
768
|
-
* published group is refused and the stale id would lose the write.
|
|
769
|
-
*/
|
|
770
|
-
patchGroupId?: string;
|
|
771
|
-
withPatchIds: PatchId[];
|
|
772
|
-
};
|
|
773
726
|
export type SaveSourceFilePatchResult = result.Result<{
|
|
774
727
|
patchId: PatchId;
|
|
775
|
-
/**
|
|
776
|
-
* The group the patch ended up in, where the store has groups at all.
|
|
777
|
-
*
|
|
778
|
-
* Absent in `fs` mode and against a content API that predates groups. The
|
|
779
|
-
* client uses it to learn the id of the group its own first write created.
|
|
780
|
-
*/
|
|
781
|
-
patchGroupId?: string;
|
|
782
728
|
/**
|
|
783
729
|
* The chain version this write made — see
|
|
784
730
|
* {@link OrderedPatches.headVersion}. Absent in `fs` mode and against a
|
|
@@ -928,37 +874,20 @@ export type PatchReadError = {
|
|
|
928
874
|
message: string;
|
|
929
875
|
};
|
|
930
876
|
/**
|
|
931
|
-
* The patches a json entry render should apply, out of the whole chain
|
|
932
|
-
*
|
|
933
|
-
* Three rules, and the second is the one that was missing. A draft page renders
|
|
934
|
-
* `jsonValues` entries beside module content, and only the modules were scoped
|
|
935
|
-
* — so one screen showed the caller's own view for its modules and base plus
|
|
936
|
-
* EVERY pending patch on the branch for the entries beside them, including
|
|
937
|
-
* another author's half-finished edit rendered as though it were live.
|
|
877
|
+
* The patches a json entry render should apply, out of the whole chain:
|
|
938
878
|
*
|
|
939
879
|
* 1. this module's, since the chain is branch-wide;
|
|
940
|
-
* 2. this
|
|
941
|
-
*
|
|
942
|
-
*
|
|
943
|
-
* nobody's to hold back, and this build does not have it. One already in
|
|
944
|
-
* the build is dropped. `draftOverlay` decides which is which, from
|
|
945
|
-
* `commits`.
|
|
946
|
-
*
|
|
947
|
-
* Filtered here rather than by asking `fetchPatches` for a list, and that is
|
|
948
|
-
* load-bearing: both implementations read an empty `patchIds` as "no filter"
|
|
949
|
-
* and return the whole chain. That is the right default for a caller that
|
|
950
|
-
* cannot mean "none", and the most dangerous possible reading of a group that
|
|
951
|
-
* is genuinely empty — it would render every unpublished patch on the branch
|
|
952
|
-
* instead of base. It costs no round trip either: the whole chain is what the
|
|
953
|
-
* unscoped path fetches anyway.
|
|
880
|
+
* 2. a patch published after this build is kept: this build does not have it.
|
|
881
|
+
* One already in the build is dropped. `draftOverlay` decides which is
|
|
882
|
+
* which, from `commits`.
|
|
954
883
|
*/
|
|
955
|
-
export declare function
|
|
884
|
+
export declare function draftModulePatches<T extends {
|
|
956
885
|
path: ModuleFilePath;
|
|
957
886
|
patchId: PatchId;
|
|
958
887
|
appliedAt: {
|
|
959
888
|
commitSha: CommitSha;
|
|
960
889
|
} | null;
|
|
961
|
-
}>(patches: T[], moduleFilePath: ModuleFilePath,
|
|
890
|
+
}>(patches: T[], moduleFilePath: ModuleFilePath,
|
|
962
891
|
/** The commits content says came after this build: see `draftOverlay`. */
|
|
963
892
|
commits?: Pick<ValCommit, "commitSha">[]): T[];
|
|
964
893
|
/**
|
|
@@ -1027,14 +956,6 @@ export type OrderedPatches = {
|
|
|
1027
956
|
* reasons.
|
|
1028
957
|
*/
|
|
1029
958
|
headVersion?: number;
|
|
1030
|
-
/**
|
|
1031
|
-
* Every group on the branch and which of these patches each holds, read in
|
|
1032
|
-
* the same transaction as the list and {@link headVersion}.
|
|
1033
|
-
*
|
|
1034
|
-
* Absent where the content service has no groups, or predates sending them
|
|
1035
|
-
* here. Only `ValOpsHttp` fills it.
|
|
1036
|
-
*/
|
|
1037
|
-
patchGroups?: PatchGroupT[];
|
|
1038
959
|
/**
|
|
1039
960
|
* Of these patches, the ones a publish job that is still running holds. A
|
|
1040
961
|
* Studio does not offer Publish over them, on any tab or device. Reported
|
|
@@ -1065,8 +986,6 @@ export type OrderedPatchesMetadata = {
|
|
|
1065
986
|
headPatchId?: OrderedPatches["headPatchId"];
|
|
1066
987
|
/** See {@link OrderedPatches.headVersion}. */
|
|
1067
988
|
headVersion?: OrderedPatches["headVersion"];
|
|
1068
|
-
/** See {@link OrderedPatches.patchGroups}. */
|
|
1069
|
-
patchGroups?: OrderedPatches["patchGroups"];
|
|
1070
989
|
/** See {@link OrderedPatches.publishingPatches}. */
|
|
1071
990
|
publishingPatches?: OrderedPatches["publishingPatches"];
|
|
1072
991
|
/** See {@link OrderedPatches.heldBackPatches}. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PatchId, ModuleFilePath, ValModules } from "@valbuild/core";
|
|
2
|
-
import { AuthorId, BaseSha, BinaryFileType, BinaryFileReader, GenericErrorMessage, MetadataOfType, OpsMetadata, PreparedCommit, ValOps, ValOpsOptions, WithGenericError, SaveSourceFilePatchResult,
|
|
2
|
+
import { AuthorId, BaseSha, BinaryFileType, BinaryFileReader, GenericErrorMessage, MetadataOfType, OpsMetadata, PreparedCommit, ValOps, ValOpsOptions, WithGenericError, SaveSourceFilePatchResult, SchemaSha, CommitSha, OrderedPatches, OrderedPatchesMetadata, SourcesSha } from "./ValOps.js";
|
|
3
3
|
import { Patch, ParentRef, ValCommit } from "@valbuild/shared/internal";
|
|
4
4
|
import type { HistoryError } from "./history/HistoryError.js";
|
|
5
5
|
import type { AffectedFile, StoredModuleVersion, CommitPage, CommitPatch, HistoricalCommit } from "./history/types.js";
|
|
@@ -112,7 +112,7 @@ export declare class ValOpsFS extends ValOps {
|
|
|
112
112
|
excludePatchOps: ExcludePatchOps;
|
|
113
113
|
}): Promise<ExcludePatchOps extends true ? OrderedPatchesMetadata : OrderedPatches>;
|
|
114
114
|
private parseJsonFile;
|
|
115
|
-
protected saveSourceFilePatch(path: ModuleFilePath, patch: Patch, patchId: PatchId, _parentRef: ParentRef, authorId: AuthorId | null, sessionId: string | null
|
|
115
|
+
protected saveSourceFilePatch(path: ModuleFilePath, patch: Patch, patchId: PatchId, _parentRef: ParentRef, authorId: AuthorId | null, sessionId: string | null): Promise<SaveSourceFilePatchResult>;
|
|
116
116
|
protected getSourceFile(path: string): Promise<WithGenericError<{
|
|
117
117
|
data: string;
|
|
118
118
|
}>>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { type ValContentVersion, type ValProposal } from "./proposal.js";
|
|
2
2
|
import { type PatchId, type ModuleFilePath, ValModules } from "@valbuild/core";
|
|
3
3
|
import type { Patch as PatchT, ParentRef as ParentRefT } from "@valbuild/core/patch";
|
|
4
|
-
import { type AuthorId, type BaseSha, BinaryFileType, type CommitSha, GenericErrorMessage, MetadataOfType, OpsMetadata, PreparedCommit, ValOps, ValOpsOptions, WithGenericError, SaveSourceFilePatchResult,
|
|
4
|
+
import { type AuthorId, type BaseSha, BinaryFileType, type CommitSha, GenericErrorMessage, MetadataOfType, OpsMetadata, PreparedCommit, ValOps, ValOpsOptions, WithGenericError, SaveSourceFilePatchResult, SchemaSha, OrderedPatchesMetadata, OrderedPatches, SourcesSha, type PublishRefusal } from "./ValOps.js";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import type { HistoryError } from "./history/HistoryError.js";
|
|
7
7
|
import type { AffectedFile, StoredModuleVersion, CommitPage, CommitPatch, HistoricalCommit } from "./history/types.js";
|
|
8
|
-
import { ParentRef, ValCommit, ValDeployment
|
|
8
|
+
import { ParentRef, ValCommit, ValDeployment } from "@valbuild/shared/internal";
|
|
9
9
|
import { result } from "@valbuild/core/fp";
|
|
10
10
|
declare const PatchId: z.ZodString & z.ZodType<PatchId, string, z.core.$ZodTypeInternals<PatchId, string>>;
|
|
11
11
|
declare const CommitSha: z.ZodString & z.ZodType<CommitSha, string, z.core.$ZodTypeInternals<CommitSha, string>>;
|
|
@@ -21,17 +21,6 @@ declare const ModuleFilePath: z.ZodString & z.ZodType<ModuleFilePath, string, z.
|
|
|
21
21
|
* be picked, and a wrong one would floor this client at another chain's count.
|
|
22
22
|
*/
|
|
23
23
|
export declare function ownBranchVersion(headVersions: Record<string, number> | undefined, branch: string | undefined): number | undefined;
|
|
24
|
-
export type PatchGroupMutationResult = {
|
|
25
|
-
patchIds: PatchId[];
|
|
26
|
-
patchGroupId: string | null;
|
|
27
|
-
headVersion?: number;
|
|
28
|
-
status?: undefined;
|
|
29
|
-
error?: undefined;
|
|
30
|
-
} | {
|
|
31
|
-
patchIds: PatchId[];
|
|
32
|
-
status: 403 | 409 | 500;
|
|
33
|
-
error: GenericErrorMessage;
|
|
34
|
-
};
|
|
35
24
|
export declare class ValOpsHttp extends ValOps {
|
|
36
25
|
private readonly contentUrl;
|
|
37
26
|
private readonly project;
|
|
@@ -320,6 +309,33 @@ export declare class ValOpsHttp extends ValOps {
|
|
|
320
309
|
body: string;
|
|
321
310
|
contentType: string;
|
|
322
311
|
}>;
|
|
312
|
+
/**
|
|
313
|
+
* Update from the site, for a proposal that changes only content
|
|
314
|
+
* (valbuild/home `docs/hosted-publishing.md`, step 8.2, slice 1a): content
|
|
315
|
+
* hands over the three versions of every module the proposal saved, this
|
|
316
|
+
* server merges them under its own build's schemas -- the same on all
|
|
317
|
+
* three sides, which is what "content only" means -- and content commits
|
|
318
|
+
* the merge on the proposal's branch.
|
|
319
|
+
*
|
|
320
|
+
* Answered here rather than forwarded, at `POST /<name>/update-content`, and
|
|
321
|
+
* only by the proposal's own server: the schemas it merges under are this
|
|
322
|
+
* build's. A conflict comes back for a person to resolve, and the same call
|
|
323
|
+
* with their `resolutions` merges.
|
|
324
|
+
*/
|
|
325
|
+
private updateContentFromSite;
|
|
326
|
+
/**
|
|
327
|
+
* Update from the site, for a proposal with code (valbuild/home
|
|
328
|
+
* `docs/hosted-publishing.md`, step 8.2, slice 1b), at
|
|
329
|
+
* `POST /<name>/update-code`. The builder tab has read content's trees
|
|
330
|
+
* (`GET /<name>/merge-trees`, forwarded) and evaluated them; this reads the
|
|
331
|
+
* trees again -- the tab's evaluation is of the trees content has, or it is
|
|
332
|
+
* of nothing -- and does the text (`updateWithCode`). The first call
|
|
333
|
+
* answers the merge for the tab to evaluate; the second, with that
|
|
334
|
+
* evaluation, commits it on the proposal's branch, staging its code.
|
|
335
|
+
*
|
|
336
|
+
* Only at the proposal's own address, as every write to its files is.
|
|
337
|
+
*/
|
|
338
|
+
private updateCodeFromSite;
|
|
323
339
|
/**
|
|
324
340
|
* A publish job's prepare, sent to content: the job's sources, archived
|
|
325
341
|
* there before the job is built (valbuild/home, docs/app-mode.md, "The
|
|
@@ -391,8 +407,6 @@ export declare class ValOpsHttp extends ValOps {
|
|
|
391
407
|
headVersion?: number;
|
|
392
408
|
/** The newest commit, which is the publish head. */
|
|
393
409
|
headCommitSha?: string;
|
|
394
|
-
/** Who holds what, at `headVersion`. See {@link OrderedPatches.patchGroups}. */
|
|
395
|
-
patchGroups?: PatchGroupT[];
|
|
396
410
|
} | {
|
|
397
411
|
type: "error";
|
|
398
412
|
error: GenericErrorMessage;
|
|
@@ -430,148 +444,7 @@ export declare class ValOpsHttp extends ValOps {
|
|
|
430
444
|
patchIds?: PatchId[];
|
|
431
445
|
excludePatchOps: ExcludePatchOps;
|
|
432
446
|
}): Promise<ExcludePatchOps extends true ? OrderedPatchesMetadata : OrderedPatches>;
|
|
433
|
-
|
|
434
|
-
* Add patches to a patch group.
|
|
435
|
-
*
|
|
436
|
-
* The set arrives closed by the client — `withPatchIds` is the prefix closure
|
|
437
|
-
* over the patch sets the staged patches belong to. We forward it and do not
|
|
438
|
-
* second-guess it: deriving the closure needs the content schema, which this
|
|
439
|
-
* process does have but content.val.build does not, and having two
|
|
440
|
-
* implementations of the rule would be worse than having one.
|
|
441
|
-
*
|
|
442
|
-
* Membership rows are stamped with `coreVersion` on the content side — the
|
|
443
|
-
* same stamp the patch row carries — so which client wrote a row stays
|
|
444
|
-
* legible after the fact.
|
|
445
|
-
*/
|
|
446
|
-
stagePatches(
|
|
447
|
-
/**
|
|
448
|
-
* The group to stage into, or `undefined` for the caller's open group on
|
|
449
|
-
* this branch — `~` on the content API, which creates it if there is none.
|
|
450
|
-
*/
|
|
451
|
-
patchGroupId: string | undefined,
|
|
452
|
-
/** What the user asked to stage. */
|
|
453
|
-
patchIds: PatchId[],
|
|
454
|
-
/**
|
|
455
|
-
* What has to come with it, because the staged patches are written on top
|
|
456
|
-
* of it.
|
|
457
|
-
*
|
|
458
|
-
* The content API stores each membership row as `explicit` or `dependency`
|
|
459
|
-
* and treats what it is not told about as a dependency. Folding the two
|
|
460
|
-
* halves into `patchIds` therefore files the patch somebody clicked as one
|
|
461
|
-
* the closure dragged in — the exact opposite of what happened, and the
|
|
462
|
-
* only record anywhere of what the author chose.
|
|
463
|
-
*/
|
|
464
|
-
withPatchIds: PatchId[],
|
|
465
|
-
/**
|
|
466
|
-
* WHO is asking, so the content API can refuse a group that is not theirs.
|
|
467
|
-
*
|
|
468
|
-
* Every call from this class carries the app's API key, which says which
|
|
469
|
-
* PROJECT is calling and nothing about which editor. Without this the
|
|
470
|
-
* content API cannot tell one of a project's editors from another, so the
|
|
471
|
-
* only check on stage and unstage is the one in `ValServer` — and anything
|
|
472
|
-
* reaching the content API by another route (an API key, a PAT) has none at
|
|
473
|
-
* all.
|
|
474
|
-
*
|
|
475
|
-
* `null` where there is no session. The content API refuses rather than
|
|
476
|
-
* treating that as a match: a group written by an api key has a null author
|
|
477
|
-
* too, and `null === null` must not read as ownership.
|
|
478
|
-
*/
|
|
479
|
-
authorId: AuthorId | null): Promise<PatchGroupMutationResult>;
|
|
480
|
-
/**
|
|
481
|
-
* Remove patches from a patch group.
|
|
482
|
-
*
|
|
483
|
-
* The set arrives closed FORWARDS by the client: unstaging a patch also
|
|
484
|
-
* unstages everything built on top of it within its patch sets, and that is
|
|
485
|
-
* what `withPatchIds` carries.
|
|
486
|
-
*/
|
|
487
|
-
unstagePatches(
|
|
488
|
-
/** See {@link stagePatches}. With no open group, there is nothing to remove. */
|
|
489
|
-
patchGroupId: string | undefined,
|
|
490
|
-
/** What the user asked to unstage. */
|
|
491
|
-
patchIds: PatchId[],
|
|
492
|
-
/** What has to go with it: everything built on top of it. */
|
|
493
|
-
withPatchIds: PatchId[],
|
|
494
|
-
/** See {@link stagePatches} — the content API's half of the ownership check. */
|
|
495
|
-
authorId: AuthorId | null): Promise<PatchGroupMutationResult>;
|
|
496
|
-
/**
|
|
497
|
-
* Every patch group on this branch, with what each holds.
|
|
498
|
-
*
|
|
499
|
-
* Read rather than mutated, and used to answer "which pending patches is THIS
|
|
500
|
-
* person allowed to see". A draft render that skips this shows base + every
|
|
501
|
-
* pending patch on the branch, including work other people have not
|
|
502
|
-
* published — which is what independent publish exists to prevent.
|
|
503
|
-
*
|
|
504
|
-
* A failure is an empty list rather than a throw, and the caller decides what
|
|
505
|
-
* that means. For a draft render the honest fallback is "show nothing
|
|
506
|
-
* pending" rather than "show everything": being shown your own committed
|
|
507
|
-
* content when the group lookup is down is a worse experience than being
|
|
508
|
-
* shown somebody else's unpublished draft is a bug.
|
|
509
|
-
*/
|
|
510
|
-
/**
|
|
511
|
-
* The last group lookup, and when it was made.
|
|
512
|
-
*
|
|
513
|
-
* A draft render calls `getPatchGroups` once per `fetchVal`, in series with
|
|
514
|
-
* the whole-chain fetch, and a page that calls `fetchVal` several times pays
|
|
515
|
-
* the round trip several times. Groups are per branch and change rarely, so a
|
|
516
|
-
* short window removes the multiplier without letting a stage go unseen for
|
|
517
|
-
* meaningfully longer than one render.
|
|
518
|
-
*
|
|
519
|
-
* Deliberately short. This is a read whose staleness decides whose draft
|
|
520
|
-
* content someone sees, so it is a per-request de-duplication rather than a
|
|
521
|
-
* cache: a second render a second later asks again.
|
|
522
|
-
*/
|
|
523
|
-
private patchGroupsCache;
|
|
524
|
-
/**
|
|
525
|
-
* Forget the remembered group list, after a write through this server.
|
|
526
|
-
*
|
|
527
|
-
* A write changes membership -- a save joins its author's group, a delete or
|
|
528
|
-
* an unstage leaves it -- and the window above is long enough for the next
|
|
529
|
-
* render to be served the list from before it: an editor who saved and
|
|
530
|
-
* reloaded inside a second was shown their draft without the change they had
|
|
531
|
-
* just made. Called once the write has answered, so a read that started
|
|
532
|
-
* while it was in flight cannot leave the old list behind it.
|
|
533
|
-
*
|
|
534
|
-
* Only this instance's writes. Another instance's write is still seen at
|
|
535
|
-
* most one window late, which is what the window is for.
|
|
536
|
-
*/
|
|
537
|
-
private forgetPatchGroups;
|
|
538
|
-
getPatchGroups(options?: {
|
|
539
|
-
/**
|
|
540
|
-
* Ask the content API even if a recent answer is remembered.
|
|
541
|
-
*
|
|
542
|
-
* For the checks that DECIDE something rather than render something.
|
|
543
|
-
* `refuseUnlessOwn` reads this list to say whether a group is yours, and a
|
|
544
|
-
* group is at its youngest exactly when that matters: the first write
|
|
545
|
-
* creates it, the save response names it, and the shell flushes its queued
|
|
546
|
-
* stages immediately — well inside the cache window. Served from a list
|
|
547
|
-
* fetched before the group existed, every one of those was refused 403 and
|
|
548
|
-
* dropped, so the queue that exists to survive the post-publish window
|
|
549
|
-
* persisted nothing in the flow it was built for.
|
|
550
|
-
*
|
|
551
|
-
* Not solved by shortening the window: the cache sits on this instance,
|
|
552
|
-
* which outlives the request, so "recent" is recent for the whole server
|
|
553
|
-
* and not for one render.
|
|
554
|
-
*/
|
|
555
|
-
fresh?: boolean;
|
|
556
|
-
}): Promise<{
|
|
557
|
-
status: "ok";
|
|
558
|
-
patchGroups: PatchGroupT[];
|
|
559
|
-
} | {
|
|
560
|
-
status: "unsupported";
|
|
561
|
-
} | {
|
|
562
|
-
status: "error";
|
|
563
|
-
message: string;
|
|
564
|
-
}>;
|
|
565
|
-
private fetchPatchGroups;
|
|
566
|
-
/**
|
|
567
|
-
* Which branch `~` means, where this build knows: the branch it reads
|
|
568
|
-
* `applicable/patches` on, so the group staged into is the group listed.
|
|
569
|
-
* Without one the content API uses the project's own, as it does for reads.
|
|
570
|
-
*/
|
|
571
|
-
private ownGroupBranch;
|
|
572
|
-
private mutatePatchGroup;
|
|
573
|
-
private mutatePatchGroupRequest;
|
|
574
|
-
protected saveSourceFilePatch(path: ModuleFilePath, patch: PatchT, patchId: PatchId, parentRef: ParentRefT, authorId: AuthorId | null, sessionId: string | null, patchGroup?: PatchGroupMembership): Promise<SaveSourceFilePatchResult>;
|
|
447
|
+
protected saveSourceFilePatch(path: ModuleFilePath, patch: PatchT, patchId: PatchId, parentRef: ParentRefT, authorId: AuthorId | null, sessionId: string | null): Promise<SaveSourceFilePatchResult>;
|
|
575
448
|
/**
|
|
576
449
|
* @deprecated For HTTP ops use direct upload instead (i.e. client should upload the files directly) since hosting platforms (Vercel) might have low limits on the size of the request body.
|
|
577
450
|
*/
|
|
@@ -587,17 +460,7 @@ export declare class ValOpsHttp extends ValOps {
|
|
|
587
460
|
getBase64EncodedBinaryFileFromPatch(filePath: string, patchId: PatchId, remote: boolean): Promise<Buffer | null>;
|
|
588
461
|
protected getBase64EncodedBinaryFileMetadataFromPatch<T extends "file" | "image">(filePath: string, type: T, patchId: PatchId, remote: boolean): Promise<OpsMetadata<T>>;
|
|
589
462
|
protected getBinaryFileMetadata<T extends "file" | "image">(filePath: string, type: T): Promise<OpsMetadata<T>>;
|
|
590
|
-
deletePatches(patchIds: PatchId[]
|
|
591
|
-
/**
|
|
592
|
-
* Patches that are NOT deleted but must lose their group membership.
|
|
593
|
-
*
|
|
594
|
-
* Deleting a patch out of the middle of a patch set leaves every group
|
|
595
|
-
* still holding the rest with a non-prefix intersection — the patches after
|
|
596
|
-
* the hole were written against a view that had it. The content API cannot
|
|
597
|
-
* work out which those are (it has no schema), so the client sends the
|
|
598
|
-
* forward closure and it drops those memberships without deleting anything.
|
|
599
|
-
*/
|
|
600
|
-
unstagePatchIds?: PatchId[]): Promise<{
|
|
463
|
+
deletePatches(patchIds: PatchId[]): Promise<{
|
|
601
464
|
deleted: PatchId[];
|
|
602
465
|
errors?: undefined;
|
|
603
466
|
error?: undefined;
|
|
@@ -611,22 +474,7 @@ export declare class ValOpsHttp extends ValOps {
|
|
|
611
474
|
errors?: undefined;
|
|
612
475
|
deleted?: undefined;
|
|
613
476
|
}>;
|
|
614
|
-
commit(prepared: PreparedCommit, message: string, committer: AuthorId, filesDirectory: string, newBranch?: string
|
|
615
|
-
/**
|
|
616
|
-
* The patch group this commit EMPTIES, if it empties one.
|
|
617
|
-
*
|
|
618
|
-
* The content API closes the group it is given — and closes it WITHOUT
|
|
619
|
-
* checking that the commit shipped all of it, so a caller that names a
|
|
620
|
-
* group still holding work takes those patches out of every group and
|
|
621
|
-
* leaves their author unable to publish them. The client therefore sends it
|
|
622
|
-
* only when the publish accounts for everything the group still holds.
|
|
623
|
-
*
|
|
624
|
-
* Omitting it is not neutral: the commit still empties the group (the
|
|
625
|
-
* content API drops applied ids from every group), but `published_at` is
|
|
626
|
-
* never set, so the id is reused across publishes instead of a new group
|
|
627
|
-
* per publish and the "already published" refusal can never fire.
|
|
628
|
-
*/
|
|
629
|
-
patchGroupId?: string): Promise<{
|
|
477
|
+
commit(prepared: PreparedCommit, message: string, committer: AuthorId, filesDirectory: string, newBranch?: string): Promise<{
|
|
630
478
|
isNotFastForward?: boolean;
|
|
631
479
|
updatedFiles: string[];
|
|
632
480
|
commit: CommitSha;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ModuleFilePath, PatchId, ValModules } from "@valbuild/core";
|
|
2
2
|
import type { Patch, ParentRef } from "@valbuild/shared/internal";
|
|
3
|
-
import { ValOps, type AuthorId, type BaseSha, type GenericErrorMessage, type MetadataOfType, type OpsMetadata, type OrderedPatches, type OrderedPatchesMetadata, type
|
|
3
|
+
import { ValOps, type AuthorId, type BaseSha, type GenericErrorMessage, type MetadataOfType, type OpsMetadata, type OrderedPatches, type OrderedPatchesMetadata, type PreparedCommit, type SaveSourceFilePatchResult, type SchemaSha, type SourcesSha, type ValOpsOptions, type WithGenericError } from "./ValOps.js";
|
|
4
4
|
import type { HistoryError } from "./history/HistoryError.js";
|
|
5
5
|
import type { AffectedFile, CommitPage, CommitPatch, HistoricalCommit, StoredModuleVersion } from "./history/types.js";
|
|
6
6
|
import { result } from "@valbuild/core/fp";
|
|
@@ -230,7 +230,7 @@ export declare class ValOpsMemory extends ValOps {
|
|
|
230
230
|
patchIds?: PatchId[];
|
|
231
231
|
excludePatchOps: ExcludePatchOps;
|
|
232
232
|
}): Promise<ExcludePatchOps extends true ? OrderedPatchesMetadata : OrderedPatches>;
|
|
233
|
-
protected saveSourceFilePatch(path: ModuleFilePath, patch: Patch, patchId: PatchId, _parentRef: ParentRef | null, authorId: AuthorId | null, _sessionId: string | null
|
|
233
|
+
protected saveSourceFilePatch(path: ModuleFilePath, patch: Patch, patchId: PatchId, _parentRef: ParentRef | null, authorId: AuthorId | null, _sessionId: string | null): Promise<SaveSourceFilePatchResult>;
|
|
234
234
|
/** One spelling for a path, whichever the caller used. See sourceFiles. */
|
|
235
235
|
private static key;
|
|
236
236
|
/**
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { ValContentVersion, ValProposal } from "./proposal.js";
|
|
2
|
-
import { ValModules,
|
|
3
|
-
import { Api, ServerOf
|
|
2
|
+
import { ValModules, ValConfig } from "@valbuild/core";
|
|
3
|
+
import { Api, ServerOf } from "@valbuild/shared/internal";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import { AuthorId, CommitSha,
|
|
6
|
-
import { ValOpsHttp } from "./ValOpsHttp.js";
|
|
5
|
+
import { AuthorId, CommitSha, type GenericErrorMessage, type PreparedCommit } from "./ValOps.js";
|
|
7
6
|
import { type ValPatchStore } from "./ValOpsMemory.js";
|
|
8
7
|
export type ValServerOptions = {
|
|
9
8
|
route: string;
|
|
@@ -61,8 +60,6 @@ export type CommitContext = {
|
|
|
61
60
|
preparedCommit: PreparedCommit;
|
|
62
61
|
message: string;
|
|
63
62
|
authorId: AuthorId;
|
|
64
|
-
/** The patch group this commit empties, if it empties one. */
|
|
65
|
-
patchGroupId?: string;
|
|
66
63
|
/**
|
|
67
64
|
* The default: a git commit through the content API.
|
|
68
65
|
*
|
|
@@ -178,120 +175,6 @@ export type ValServerCallbacks = {
|
|
|
178
175
|
onEnable: (success: boolean) => Promise<void>;
|
|
179
176
|
onDisable: (success: boolean) => Promise<void>;
|
|
180
177
|
};
|
|
181
|
-
/**
|
|
182
|
-
* Refuse to touch a group that is not the caller's.
|
|
183
|
-
*
|
|
184
|
-
* Exported for `patchGroupOwnership.test.ts`: this is the whole of the
|
|
185
|
-
* authorization for stage and unstage, and nothing else in the process checks
|
|
186
|
-
* it, so it is worth testing as a policy rather than only through a route.
|
|
187
|
-
*
|
|
188
|
-
* `getAuth` only proves a session EXISTS; it says nothing about whose
|
|
189
|
-
* group this is. And the content API cannot decide either — every call
|
|
190
|
-
* from here carries the app's API key, not the editor's identity — so if
|
|
191
|
-
* this does not check, nothing does.
|
|
192
|
-
*
|
|
193
|
-
* `GET /patches?include_patch_groups=true` hands every editor the id and
|
|
194
|
-
* author of every open group on the branch, so without this any logged-in
|
|
195
|
-
* editor can unstage another author's patches (their next publish
|
|
196
|
-
* silently ships less) or stage into their group (it silently ships
|
|
197
|
-
* more). The 403 declared for this route in `ApiRoutes.ts` was
|
|
198
|
-
* unreachable.
|
|
199
|
-
*
|
|
200
|
-
* Fails CLOSED: if the groups cannot be read, the mutation is refused
|
|
201
|
-
* rather than allowed unverified.
|
|
202
|
-
*/
|
|
203
|
-
/**
|
|
204
|
-
* The patches a scoped draft render should apply: the caller's own group, plus
|
|
205
|
-
* everything already committed.
|
|
206
|
-
*
|
|
207
|
-
* Scoping is about PENDING work. A published patch stays in the chain with
|
|
208
|
-
* `appliedAt` set until the next deployment moves the base, and it is part of
|
|
209
|
-
* everyone's view in that window — the unscoped path applies it. Dropping it
|
|
210
|
-
* meant the moment somebody published, their own draft preview reverted the
|
|
211
|
-
* field they had just shipped, and nobody else saw it either until the deploy
|
|
212
|
-
* landed; anything written on top in that window is authored against content
|
|
213
|
-
* already stale on `main`.
|
|
214
|
-
*
|
|
215
|
-
* Keyed on `appliedAt` rather than on the group's `publishedAt`, because a
|
|
216
|
-
* PARTIAL publish leaves the group open with only some of its patches applied.
|
|
217
|
-
* Those are committed too, and no flag on the group names them.
|
|
218
|
-
*
|
|
219
|
-
* `undefined` scope is unscoped and never reaches here; an EMPTY scope is a
|
|
220
|
-
* caller holding nothing, and on a branch with nothing applied it correctly
|
|
221
|
-
* filters down to no patches, which renders base.
|
|
222
|
-
*/
|
|
223
|
-
export declare function scopedPatches<T extends {
|
|
224
|
-
patchId: PatchId;
|
|
225
|
-
appliedAt: {
|
|
226
|
-
commitSha: CommitSha;
|
|
227
|
-
} | null;
|
|
228
|
-
}>(patches: T[], ownPatchIds: PatchId[] | undefined): T[];
|
|
229
|
-
/**
|
|
230
|
-
* Which of the client's `unstagePatchIds` this server is willing to forward.
|
|
231
|
-
*
|
|
232
|
-
* The forward closure of a discard is the client's to compute — it needs the
|
|
233
|
-
* patch sets, which need the schema — and it was being forwarded verbatim. But
|
|
234
|
-
* the content API removes those memberships from EVERY group with no ownership
|
|
235
|
-
* check, so any logged-in editor could strip arbitrary patches out of any other
|
|
236
|
-
* author's group by attaching them to a delete of one of their own throwaway
|
|
237
|
-
* patches. That is the outcome the 403 on `/patch-groups` exists to prevent,
|
|
238
|
-
* reached by a different door: their next publish silently ships less.
|
|
239
|
-
*
|
|
240
|
-
* Neither server can compute the true closure, but this one can BOUND it. A
|
|
241
|
-
* patch can only be invalidated by a delete if it was written after that delete
|
|
242
|
-
* — its paths were chosen against a view that had it — and if it is in the same
|
|
243
|
-
* module, since a patch set never spans two. Anything outside those bounds was
|
|
244
|
-
* not in the closure whatever the client says, so it is dropped rather than
|
|
245
|
-
* refused: the delete is still correct, and refusing the whole request over an
|
|
246
|
-
* over-broad extra would turn a discard into an error the user cannot act on.
|
|
247
|
-
*
|
|
248
|
-
* Exported for the test. Pure, and given the chain rather than fetching it, so
|
|
249
|
-
* the ordering it depends on is visible in the test rather than mocked.
|
|
250
|
-
*/
|
|
251
|
-
export declare function boundUnstageClosure(
|
|
252
|
-
/** The pending chain, in chain order, as `fetchPatches` returns it. */
|
|
253
|
-
chain: readonly {
|
|
254
|
-
patchId: PatchId;
|
|
255
|
-
path: ModuleFilePath;
|
|
256
|
-
}[], deleted: readonly PatchId[], requested: readonly PatchId[]): PatchId[];
|
|
257
|
-
/**
|
|
258
|
-
* Which pending patches this caller may see, when they asked for "only mine".
|
|
259
|
-
*
|
|
260
|
-
* Shared by `/sources/~` and `/json`, and it has to be: a draft page renders
|
|
261
|
-
* both, so two answers to "whose work is this" put one person's half-finished
|
|
262
|
-
* edit on another person's preview through whichever route was not scoped. That
|
|
263
|
-
* is exactly what happened — `/json` applied every pending patch on the branch
|
|
264
|
-
* while the module content beside it was scoped.
|
|
265
|
-
*
|
|
266
|
-
* `undefined` means "apply everything", which is what every caller that does
|
|
267
|
-
* not ask for scoping gets and must keep getting.
|
|
268
|
-
*/
|
|
269
|
-
/** The patches in this author's OPEN groups: what a scoped draft shows. */
|
|
270
|
-
export declare function ownPatchIdsIn(patchGroups: PatchGroupT[], authorId: string): PatchId[];
|
|
271
|
-
/**
|
|
272
|
-
* Whose pending patches a scoped draft shows, taken from the SAME response as
|
|
273
|
-
* the patches when it says, and from the list read beforehand only when not.
|
|
274
|
-
*
|
|
275
|
-
* See the call in `/sources/~`: the list read beforehand can be older than the
|
|
276
|
-
* chain, and the response's membership cannot be.
|
|
277
|
-
*/
|
|
278
|
-
export declare function ownPatchScopeFor(fetched: {
|
|
279
|
-
patchGroups?: PatchGroupT[];
|
|
280
|
-
}, authorId: string | undefined, remembered: PatchId[] | undefined): PatchId[] | undefined;
|
|
281
|
-
export declare function resolveOwnPatchScope(serverOps: ValOps, opts: {
|
|
282
|
-
/** A caller that named a list already knows what it wants. */
|
|
283
|
-
explicitPatchIds: PatchId[] | undefined;
|
|
284
|
-
ownGroupsOnly: boolean;
|
|
285
|
-
/** `undefined` where there is no session to have one. */
|
|
286
|
-
authorId: string | undefined;
|
|
287
|
-
}): Promise<{
|
|
288
|
-
ownPatchIds: PatchId[] | undefined;
|
|
289
|
-
scopeAlsoIncludesApplied: boolean;
|
|
290
|
-
}>;
|
|
291
|
-
export declare function refuseUnlessOwn(ops: ValOpsHttp, patchGroupId: string, authorId: string): Promise<{
|
|
292
|
-
status: 403 | 409 | 500;
|
|
293
|
-
message: string;
|
|
294
|
-
} | null>;
|
|
295
178
|
declare const IntegratedServerJwtPayload: z.ZodObject<{
|
|
296
179
|
sub: z.ZodString;
|
|
297
180
|
exp: z.ZodNumber;
|