@valbuild/server 0.102.0 → 0.103.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/dist/declarations/src/ValOps.d.ts +12 -3
- package/dist/declarations/src/ValOpsFS.d.ts +46 -37
- package/dist/declarations/src/createFixPatch.d.ts +12 -1
- package/dist/declarations/src/fixHandlers.d.ts +162 -0
- package/dist/declarations/src/getValidationErrorFileRef.d.ts +6 -0
- package/dist/declarations/src/index.d.ts +9 -0
- package/dist/declarations/src/patch/ts/syntax.d.ts +0 -14
- package/dist/declarations/src/patchLog.d.ts +124 -0
- package/dist/declarations/src/patchStore.d.ts +216 -0
- package/dist/valbuild-server.cjs.dev.js +2243 -1013
- package/dist/valbuild-server.cjs.prod.js +2243 -1013
- package/dist/valbuild-server.esm.js +2232 -1016
- package/package.json +4 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FileMetadata, FileSource, ImageMetadata, ModuleFilePath, PatchId,
|
|
1
|
+
import { MediaSource, FileMetadata, FileSource, ImageMetadata, ModuleFilePath, PatchId, Schema, SelectorSource, SerializedSchema, Source, SourcePath, ValConfig, ValModules, ValidationError } from "@valbuild/core";
|
|
2
2
|
import { result } from "@valbuild/core/fp";
|
|
3
3
|
import { JSONValue, ParentRef, Patch, PatchError } from "@valbuild/core/patch";
|
|
4
4
|
import { ValSyntaxError, ValSyntaxErrorTree } from "./patch/ts/syntax.js";
|
|
@@ -84,6 +84,15 @@ export declare abstract class ValOps {
|
|
|
84
84
|
schemaSha: SchemaSha;
|
|
85
85
|
sourcesSha: SourcesSha;
|
|
86
86
|
patches: PatchId[];
|
|
87
|
+
/**
|
|
88
|
+
* Unpublished changes the store threw away because it could not read
|
|
89
|
+
* them. FS mode only: the content api owns its own patches and does not
|
|
90
|
+
* discard them behind the client's back.
|
|
91
|
+
*/
|
|
92
|
+
removed?: {
|
|
93
|
+
patchId: PatchId;
|
|
94
|
+
reason: string;
|
|
95
|
+
}[];
|
|
87
96
|
/** FS mode only — see the `params` counterpart. */
|
|
88
97
|
jsonEntriesSha?: string;
|
|
89
98
|
} | {
|
|
@@ -225,9 +234,9 @@ export declare abstract class ValOps {
|
|
|
225
234
|
validations: Record<SourcePath, ValidationError[]>;
|
|
226
235
|
}>;
|
|
227
236
|
files: Record<SourcePath, FileSource>;
|
|
228
|
-
remoteFiles: Record<SourcePath,
|
|
237
|
+
remoteFiles: Record<SourcePath, MediaSource>;
|
|
229
238
|
}>;
|
|
230
|
-
validateRemoteFiles(schemas: Schemas, sources: Sources, remoteFiles: Record<SourcePath,
|
|
239
|
+
validateRemoteFiles(schemas: Schemas, sources: Sources, remoteFiles: Record<SourcePath, MediaSource>): Promise<Record<SourcePath, ValidationError[]>>;
|
|
231
240
|
validateFiles(schemas: Schemas, sources: Sources, files: Record<SourcePath, FileSource>, fileLastUpdatedByPatchId?: PatchAnalysis["fileLastUpdatedByPatchId"]): Promise<Record<SourcePath, ValidationError[]>>;
|
|
232
241
|
/**
|
|
233
242
|
* Applies the pending patches to the source files so they can be committed.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { PatchId, ModuleFilePath, ValModules } from "@valbuild/core";
|
|
2
|
-
import { AuthorId, BaseSha, BinaryFileType, GenericErrorMessage, MetadataOfType, OpsMetadata, PreparedCommit, ValOps, ValOpsOptions, WithGenericError, SaveSourceFilePatchResult, SchemaSha, CommitSha, OrderedPatches, OrderedPatchesMetadata,
|
|
2
|
+
import { AuthorId, BaseSha, BinaryFileType, 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
|
-
import { ParentPatchId } from "@valbuild/core";
|
|
5
4
|
import { Buffer } from "buffer";
|
|
6
5
|
export declare class ValOpsFS extends ValOps {
|
|
7
6
|
private readonly contentUrl;
|
|
@@ -14,6 +13,20 @@ export declare class ValOpsFS extends ValOps {
|
|
|
14
13
|
* content lives behind a thunk that `JSON.stringify` drops).
|
|
15
14
|
*/
|
|
16
15
|
private readonly jsonEntryFilesFingerprint;
|
|
16
|
+
/**
|
|
17
|
+
* Unpublished changes repair threw away, waiting to be told to somebody.
|
|
18
|
+
*
|
|
19
|
+
* Held here and drained by the next `getStat`, because stat is the channel
|
|
20
|
+
* that always flows. The case worth reporting is a repair that removed
|
|
21
|
+
* EVERYTHING - which is what an old store looks like - and then there is
|
|
22
|
+
* nothing left for the studio to fetch, so a notice riding on `fetchPatches`
|
|
23
|
+
* would never be collected. Drained rather than kept, because this has to be
|
|
24
|
+
* said once: a permanent flag would put a toast on the screen forever.
|
|
25
|
+
*
|
|
26
|
+
* Held in memory only: a restart loses it, and that is the right trade. The
|
|
27
|
+
* durable record is `patches.repair.log`.
|
|
28
|
+
*/
|
|
29
|
+
private readonly removedPatchNotices;
|
|
17
30
|
onInit(): Promise<void>;
|
|
18
31
|
getPresignedAuthNonce(project: string, corsOrigin: string, auth: {
|
|
19
32
|
pat: string;
|
|
@@ -50,6 +63,10 @@ export declare class ValOpsFS extends ValOps {
|
|
|
50
63
|
schemaSha: SchemaSha;
|
|
51
64
|
sourcesSha: SourcesSha;
|
|
52
65
|
patches: PatchId[];
|
|
66
|
+
removed?: {
|
|
67
|
+
patchId: PatchId;
|
|
68
|
+
reason: string;
|
|
69
|
+
}[];
|
|
53
70
|
jsonEntriesSha?: string;
|
|
54
71
|
} | {
|
|
55
72
|
type: "use-websocket";
|
|
@@ -67,23 +84,39 @@ export declare class ValOpsFS extends ValOps {
|
|
|
67
84
|
unauthorized?: boolean;
|
|
68
85
|
networkError?: boolean;
|
|
69
86
|
}>;
|
|
70
|
-
|
|
71
|
-
|
|
87
|
+
/**
|
|
88
|
+
* The one read every other read comes out of.
|
|
89
|
+
*
|
|
90
|
+
* `getStat` announces from this and `fetchPatches` delivers from this, so the
|
|
91
|
+
* two cannot disagree. They used to: `getStat` counted the directories on disk
|
|
92
|
+
* while `fetchPatches` walked the parent links between them, and when a single
|
|
93
|
+
* record went missing the first said 410 and the second said 359 — with no
|
|
94
|
+
* error anywhere, because a walk that runs out of links just stops.
|
|
95
|
+
*
|
|
96
|
+
* Anything wrong is reported, then repaired, and reset only if repair does not
|
|
97
|
+
* settle it. In that order, and never silently.
|
|
98
|
+
*/
|
|
99
|
+
private readStore;
|
|
100
|
+
/**
|
|
101
|
+
* Report, repair, and reset only if repair did not take.
|
|
102
|
+
*
|
|
103
|
+
* Reached only when something is actually wrong, so the healthy path — which
|
|
104
|
+
* is every stat poll — never touches the lock.
|
|
105
|
+
*/
|
|
106
|
+
private repairStore;
|
|
72
107
|
fetchPatches<ExcludePatchOps extends boolean>(filters: {
|
|
73
108
|
patchIds?: PatchId[];
|
|
74
109
|
excludePatchOps: ExcludePatchOps;
|
|
75
110
|
}): Promise<ExcludePatchOps extends true ? OrderedPatchesMetadata : OrderedPatches>;
|
|
76
|
-
fetchPatchesFromFS<ExcludePatchOps extends boolean>(excludePatchOps: ExcludePatchOps): Promise<ExcludePatchOps extends true ? FSPatchesMetadata : FSPatches>;
|
|
77
|
-
private createPatchChain;
|
|
78
111
|
private parseJsonFile;
|
|
79
|
-
protected saveSourceFilePatch(path: ModuleFilePath, patch: Patch, patchId: PatchId,
|
|
112
|
+
protected saveSourceFilePatch(path: ModuleFilePath, patch: Patch, patchId: PatchId, _parentRef: ParentRef, authorId: AuthorId | null, sessionId: string | null): Promise<SaveSourceFilePatchResult>;
|
|
80
113
|
protected getSourceFile(path: string): Promise<WithGenericError<{
|
|
81
114
|
data: string;
|
|
82
115
|
}>>;
|
|
83
116
|
protected saveSourceFile(path: ModuleFilePath, data: string): Promise<WithGenericError<{
|
|
84
117
|
path: ModuleFilePath;
|
|
85
118
|
}>>;
|
|
86
|
-
saveBase64EncodedBinaryFileFromPatch(filePath: string,
|
|
119
|
+
saveBase64EncodedBinaryFileFromPatch(filePath: string, _parentRef: ParentRef, patchId: PatchId, data: string | null, _type: BinaryFileType, metadata: MetadataOfType<BinaryFileType> | undefined): Promise<WithGenericError<{
|
|
87
120
|
patchId: PatchId;
|
|
88
121
|
filePath: string;
|
|
89
122
|
}>>;
|
|
@@ -104,7 +137,6 @@ export declare class ValOpsFS extends ValOps {
|
|
|
104
137
|
deleteAllPatches(): Promise<{
|
|
105
138
|
error?: GenericErrorMessage;
|
|
106
139
|
}>;
|
|
107
|
-
private updateOrderedPatches;
|
|
108
140
|
saveOrUploadFiles(preparedCommit: PreparedCommit, mode: "skip-remote" | "upload-remote", auth?: {
|
|
109
141
|
apiKey: string;
|
|
110
142
|
} | {
|
|
@@ -118,33 +150,10 @@ export declare class ValOpsFS extends ValOps {
|
|
|
118
150
|
}>;
|
|
119
151
|
getBinaryFile(filePath: string): Promise<Buffer | null>;
|
|
120
152
|
protected getBinaryFileMetadata<T extends BinaryFileType>(filePath: string, type: T): Promise<OpsMetadata<T>>;
|
|
121
|
-
private getParentPatchIdFromPatchId;
|
|
122
|
-
private getParentPatchIdFromPatchIdMap;
|
|
123
153
|
private getPatchesDir;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
private
|
|
154
|
+
/**
|
|
155
|
+
* Deliberately outside the patches directory: delete-all and reset rename that
|
|
156
|
+
* whole directory, and a lock that moves away with it is not holding anything.
|
|
157
|
+
*/
|
|
158
|
+
private getPatchLockFile;
|
|
129
159
|
}
|
|
130
|
-
type FSPatches = {
|
|
131
|
-
patches: Record<PatchId, {
|
|
132
|
-
path: ModuleFilePath;
|
|
133
|
-
patch: Patch;
|
|
134
|
-
parentRef: ParentRef;
|
|
135
|
-
createdAt: string;
|
|
136
|
-
authorId: AuthorId | null;
|
|
137
|
-
baseSha: BaseSha;
|
|
138
|
-
appliedAt: null;
|
|
139
|
-
}>;
|
|
140
|
-
error?: GenericErrorMessage;
|
|
141
|
-
errors?: PatchReadError[];
|
|
142
|
-
};
|
|
143
|
-
type FSPatchesMetadata = {
|
|
144
|
-
patches: Record<PatchId, Omit<FSPatches["patches"][PatchId], "patch"> & {
|
|
145
|
-
patch?: undefined;
|
|
146
|
-
}>;
|
|
147
|
-
error?: GenericErrorMessage;
|
|
148
|
-
errors?: FSPatches["errors"];
|
|
149
|
-
};
|
|
150
|
-
export {};
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import { FileMetadata, ImageMetadata, SerializedSchema, Source, SourcePath, ValidationError } from "@valbuild/core";
|
|
2
|
-
import { Patch } from "@valbuild/core/patch";
|
|
2
|
+
import { JSONValue, Patch } from "@valbuild/core/patch";
|
|
3
3
|
export type FixPatchRemainingError = ValidationError & {
|
|
4
4
|
sourcePath?: SourcePath;
|
|
5
5
|
};
|
|
6
|
+
/**
|
|
7
|
+
* A whole media value, for the fixes that replace one outright.
|
|
8
|
+
*
|
|
9
|
+
* The three remote fixes each wrote this shape out by hand, and one of them was
|
|
10
|
+
* left behind when the shape changed. One name, so the next change cannot miss a
|
|
11
|
+
* site.
|
|
12
|
+
*
|
|
13
|
+
* `path` last is deliberate: it is the field the fix is about, and a stray
|
|
14
|
+
* `path` inside the metadata must not win over it.
|
|
15
|
+
*/
|
|
16
|
+
export declare function mediaValue(path: string, metadata: unknown): Record<string, JSONValue>;
|
|
6
17
|
export declare function createFixPatch(config: {
|
|
7
18
|
projectRoot: string;
|
|
8
19
|
remoteHost: string;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fix handlers behind `val validate --fix`, and the editor quick fixes that
|
|
3
|
+
* must agree with it.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this lives in `@valbuild/server`
|
|
6
|
+
*
|
|
7
|
+
* A fix is two layers. `createFixPatch` is the second: given a validation
|
|
8
|
+
* error it produces a patch. The first is everything that has to happen before a
|
|
9
|
+
* patch can be produced — read the file, check it is actually on disk, extract
|
|
10
|
+
* metadata, pick a remote bucket, upload the bytes, download them back. That
|
|
11
|
+
* layer used to live in `packages/cli/src/runValidation.ts`, which made it
|
|
12
|
+
* reachable only from the CLI: `@valbuild/cli` exports nothing but `./cli`, and
|
|
13
|
+
* `@valbuild/language-server` cannot depend on it anyway without creating a
|
|
14
|
+
* cycle (the CLI depends on the language server).
|
|
15
|
+
*
|
|
16
|
+
* The consequence was that an editor could offer the fixes needing no
|
|
17
|
+
* precondition and had to reimplement or skip the rest. Both happened: the
|
|
18
|
+
* VS Code extension grew its own remote-upload client, and the language server
|
|
19
|
+
* declined to offer remote fixes at all.
|
|
20
|
+
*
|
|
21
|
+
* So the layer sits here, next to `createFixPatch`, and the CLI and the language
|
|
22
|
+
* server are both callers. {@link FixHandlerContext} is deliberately made of
|
|
23
|
+
* things any caller already has — a `Service`, an `IValFSHost`, a project root —
|
|
24
|
+
* rather than of CLI concepts.
|
|
25
|
+
*
|
|
26
|
+
* What stays in the CLI is the driver: the `runValidation` async generator that
|
|
27
|
+
* walks every module, decides what to report, and renders it to a terminal.
|
|
28
|
+
*/
|
|
29
|
+
import { ModuleFilePath, SourcePath, ValidationFix } from "@valbuild/core";
|
|
30
|
+
import type { Service } from "./Service.js";
|
|
31
|
+
import type { IValFSHost } from "./ValFSHost.js";
|
|
32
|
+
export type { IValFSHost };
|
|
33
|
+
export type IValRemote = {
|
|
34
|
+
remoteHost: string;
|
|
35
|
+
getSettings(projectName: string, options: {
|
|
36
|
+
pat: string;
|
|
37
|
+
}): Promise<{
|
|
38
|
+
success: true;
|
|
39
|
+
data: {
|
|
40
|
+
publicProjectId: string;
|
|
41
|
+
remoteFileBuckets: {
|
|
42
|
+
bucket: string;
|
|
43
|
+
}[];
|
|
44
|
+
};
|
|
45
|
+
} | {
|
|
46
|
+
success: false;
|
|
47
|
+
message: string;
|
|
48
|
+
}>;
|
|
49
|
+
uploadFile(project: string, bucket: string, fileHash: string, fileExt: string | undefined, fileBuffer: Buffer, options: {
|
|
50
|
+
pat: string;
|
|
51
|
+
}): Promise<{
|
|
52
|
+
success: true;
|
|
53
|
+
} | {
|
|
54
|
+
success: false;
|
|
55
|
+
error: string;
|
|
56
|
+
}>;
|
|
57
|
+
};
|
|
58
|
+
export type ValModule = Awaited<ReturnType<Service["get"]>>;
|
|
59
|
+
export type ValidationError = {
|
|
60
|
+
message: string;
|
|
61
|
+
value?: unknown;
|
|
62
|
+
fixes?: ValidationFix[];
|
|
63
|
+
keyError?: boolean;
|
|
64
|
+
};
|
|
65
|
+
export type FixHandlerContext = {
|
|
66
|
+
sourcePath: SourcePath;
|
|
67
|
+
validationError: ValidationError;
|
|
68
|
+
valModule: ValModule;
|
|
69
|
+
projectRoot: string;
|
|
70
|
+
fix: boolean;
|
|
71
|
+
service: Service;
|
|
72
|
+
valFiles: string[];
|
|
73
|
+
moduleFilePath: ModuleFilePath;
|
|
74
|
+
file: string;
|
|
75
|
+
fs: IValFSHost;
|
|
76
|
+
remoteFiles: Record<SourcePath, {
|
|
77
|
+
ref: string;
|
|
78
|
+
metadata?: Record<string, unknown>;
|
|
79
|
+
}>;
|
|
80
|
+
publicProjectId?: string;
|
|
81
|
+
remoteFileBuckets?: string[];
|
|
82
|
+
remoteFilesCounter: number;
|
|
83
|
+
remote: IValRemote;
|
|
84
|
+
project: string | undefined;
|
|
85
|
+
};
|
|
86
|
+
export type FixHandlerResult = {
|
|
87
|
+
success: boolean;
|
|
88
|
+
errorMessage?: string;
|
|
89
|
+
shouldApplyPatch?: boolean;
|
|
90
|
+
appliedFix?: boolean;
|
|
91
|
+
fixableErrorMessage?: string;
|
|
92
|
+
publicProjectId?: string;
|
|
93
|
+
remoteFileBuckets?: string[];
|
|
94
|
+
remoteFilesCounter?: number;
|
|
95
|
+
events?: ValidationEvent[];
|
|
96
|
+
};
|
|
97
|
+
export type FixHandler = (ctx: FixHandlerContext) => Promise<FixHandlerResult>;
|
|
98
|
+
export type ValidationEvent = {
|
|
99
|
+
type: "file-valid";
|
|
100
|
+
file: string;
|
|
101
|
+
durationMs: number;
|
|
102
|
+
} | {
|
|
103
|
+
type: "file-error-count";
|
|
104
|
+
file: string;
|
|
105
|
+
errorCount: number;
|
|
106
|
+
durationMs: number;
|
|
107
|
+
} | {
|
|
108
|
+
type: "validation-error";
|
|
109
|
+
sourcePath: string;
|
|
110
|
+
message: string;
|
|
111
|
+
keyError?: boolean;
|
|
112
|
+
} | {
|
|
113
|
+
type: "validation-fixable-error";
|
|
114
|
+
sourcePath: string;
|
|
115
|
+
message: string;
|
|
116
|
+
fixable: boolean;
|
|
117
|
+
keyError?: boolean;
|
|
118
|
+
} | {
|
|
119
|
+
type: "unknown-fix";
|
|
120
|
+
sourcePath: string;
|
|
121
|
+
fixes: string[];
|
|
122
|
+
keyError?: boolean;
|
|
123
|
+
} | {
|
|
124
|
+
type: "unregistered-module";
|
|
125
|
+
file: string;
|
|
126
|
+
} | {
|
|
127
|
+
type: "fix-applied";
|
|
128
|
+
file: string;
|
|
129
|
+
sourcePath: string;
|
|
130
|
+
} | {
|
|
131
|
+
type: "fatal-error";
|
|
132
|
+
file: string;
|
|
133
|
+
message: string;
|
|
134
|
+
} | {
|
|
135
|
+
type: "remote-uploading";
|
|
136
|
+
ref: string;
|
|
137
|
+
} | {
|
|
138
|
+
type: "remote-uploaded";
|
|
139
|
+
ref: string;
|
|
140
|
+
} | {
|
|
141
|
+
type: "remote-already-uploaded";
|
|
142
|
+
filePath: string;
|
|
143
|
+
} | {
|
|
144
|
+
type: "remote-downloading";
|
|
145
|
+
sourcePath: string;
|
|
146
|
+
} | {
|
|
147
|
+
type: "summary-errors";
|
|
148
|
+
count: number;
|
|
149
|
+
} | {
|
|
150
|
+
type: "summary-success";
|
|
151
|
+
};
|
|
152
|
+
export declare function handleFileMetadata(ctx: FixHandlerContext): Promise<FixHandlerResult>;
|
|
153
|
+
export declare function handleRemoteFileUpload(ctx: FixHandlerContext): Promise<FixHandlerResult>;
|
|
154
|
+
export declare function handleRemoteGalleryFileUpload(ctx: FixHandlerContext): Promise<FixHandlerResult>;
|
|
155
|
+
export declare function handleRemoteFileDownload(ctx: FixHandlerContext): Promise<FixHandlerResult>;
|
|
156
|
+
export declare function handleRemoteFileCheck(): Promise<FixHandlerResult>;
|
|
157
|
+
export declare function handleUniqueFolderCheck(ctx: FixHandlerContext): Promise<FixHandlerResult>;
|
|
158
|
+
export declare function handleCheckAllFiles(ctx: FixHandlerContext): Promise<FixHandlerResult>;
|
|
159
|
+
export declare function handleJsonValuesExtractEntry(ctx: FixHandlerContext): Promise<FixHandlerResult>;
|
|
160
|
+
export declare const currentFixHandlers: Record<Exclude<ValidationFix, "keyof:check-keys" | "router:check-route">, FixHandler>;
|
|
161
|
+
export declare const fixHandlers: Record<string, FixHandler>;
|
|
162
|
+
export declare function createDefaultValFSHost(): IValFSHost;
|
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
import { ValidationError } from "@valbuild/core";
|
|
2
|
+
/**
|
|
3
|
+
* The media path a validation error is about, if it is about media at all.
|
|
4
|
+
*
|
|
5
|
+
* There is no schema in hand here — a `ValidationError` carries only the value
|
|
6
|
+
* it flagged — so the shape is all there is to go on.
|
|
7
|
+
*/
|
|
2
8
|
export declare function getValidationErrorFileRef(validationError: ValidationError): string | null;
|
|
@@ -11,6 +11,8 @@ export { formatSyntaxErrorTree } from "./patch/ts/syntax.js";
|
|
|
11
11
|
export { analyzeValModule } from "./patch/ts/valModule.js";
|
|
12
12
|
export type { ValModuleAnalysis } from "./patch/ts/valModule.js";
|
|
13
13
|
export { createFixPatch } from "./createFixPatch.js";
|
|
14
|
+
export { fixHandlers, currentFixHandlers, createDefaultValFSHost, handleFileMetadata, handleRemoteFileUpload, handleRemoteGalleryFileUpload, handleRemoteFileDownload, handleRemoteFileCheck, handleUniqueFolderCheck, handleCheckAllFiles, handleJsonValuesExtractEntry, } from "./fixHandlers.js";
|
|
15
|
+
export type { FixHandler, FixHandlerContext, FixHandlerResult, IValRemote, ValidationEvent, ValidationError, ValModule, } from "./fixHandlers.js";
|
|
14
16
|
export * from "./jwt.js";
|
|
15
17
|
export type { ValServer } from "./ValServer.js";
|
|
16
18
|
export { getSettings } from "./getSettings.js";
|
|
@@ -36,3 +38,10 @@ export { formatPatchSourceError } from "./ValOps.js";
|
|
|
36
38
|
export { compareWithCapturedReport, readCapturedReport, replaySnapshot, } from "./debug/replaySnapshot.js";
|
|
37
39
|
export type { ReplayComparison, ReplayResult } from "./debug/replaySnapshot.js";
|
|
38
40
|
export type { OrderedPatches, PatchAnalysis, PatchSourceError, PreparedCommit, } from "./ValOps.js";
|
|
41
|
+
/**
|
|
42
|
+
* The local-dev patch store, exported so the CLI's debug tooling can read a
|
|
43
|
+
* snapshot back with the same code the server uses rather than a second
|
|
44
|
+
* implementation of the layout.
|
|
45
|
+
*/
|
|
46
|
+
export { readPatchStore, describePatchStoreProblems } from "./patchStore.js";
|
|
47
|
+
export type { PatchStoreEntry, PatchStoreProblem, ReadPatchStoreResult, } from "./patchStore.js";
|
|
@@ -37,19 +37,5 @@ export declare function deepValidateExpression(value: ts.Expression): result.Res
|
|
|
37
37
|
*/
|
|
38
38
|
export declare function evaluateExpression(value: ts.Expression): result.Result<JSONValue, ValSyntaxErrorTree>;
|
|
39
39
|
export declare function findObjectPropertyAssignment(value: ts.ObjectLiteralExpression, key: string): result.Result<LiteralPropertyAssignment | undefined, ValSyntaxErrorTree>;
|
|
40
|
-
export declare function isValFileMethodCall(node: ts.Expression): node is ts.CallExpression;
|
|
41
|
-
export declare function isValImageMethodCall(node: ts.Expression): node is ts.CallExpression;
|
|
42
|
-
export declare function isValRemoteMethodCall(node: ts.Expression): node is ts.CallExpression;
|
|
43
|
-
export declare function findValFileNodeArg(node: ts.CallExpression): result.Result<ts.StringLiteral, ValSyntaxErrorTree>;
|
|
44
|
-
export declare function findValRemoteNodeArg(node: ts.CallExpression): result.Result<ts.StringLiteral, ValSyntaxErrorTree>;
|
|
45
|
-
export declare function findValImageNodeArg(node: ts.CallExpression): result.Result<ts.StringLiteral, ValSyntaxErrorTree>;
|
|
46
|
-
export declare function findValFileMetadataArg(node: ts.CallExpression): result.Result<ts.ObjectLiteralExpression | undefined, ValSyntaxErrorTree>;
|
|
47
|
-
export declare function findValImageMetadataArg(node: ts.CallExpression): result.Result<ts.ObjectLiteralExpression | undefined, ValSyntaxErrorTree>;
|
|
48
|
-
export declare function findValRemoteMetadataArg(node: ts.CallExpression): result.Result<ts.ObjectLiteralExpression | undefined, ValSyntaxErrorTree>;
|
|
49
|
-
/**
|
|
50
|
-
* Given a list of expressions, validates that all the expressions are not
|
|
51
|
-
* spread elements. In other words, it ensures that the expressions are the
|
|
52
|
-
* initializers of the values at their respective indices in the evaluated list.
|
|
53
|
-
*/
|
|
54
40
|
export declare function validateInitializers(nodes: ReadonlyArray<ts.Expression>): ValSyntaxErrorTree | undefined;
|
|
55
41
|
export {};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { ModuleFilePath, PatchId } from "@valbuild/core";
|
|
2
|
+
/**
|
|
3
|
+
* The order of the pending patches, held in one flat, append-only text file.
|
|
4
|
+
*
|
|
5
|
+
* ## Why a log and not links between the patches
|
|
6
|
+
*
|
|
7
|
+
* The store this replaces kept the order in the patches themselves: every record
|
|
8
|
+
* carried a `parentRef`, and the directory a record lived in was named after that
|
|
9
|
+
* parent. Reading the chain meant walking those links from `head`, and the walk
|
|
10
|
+
* stopped dead — silently — at the first id nothing on disk answered for.
|
|
11
|
+
*
|
|
12
|
+
* That is not a hypothetical. A store with 410 patches lost exactly one record,
|
|
13
|
+
* and the 51 patches written after it became unreachable: `/stat` counted the
|
|
14
|
+
* directories and announced 410, `GET /patches` walked the links and delivered
|
|
15
|
+
* 359, and the studio waited forever for 51 ids that no longer had a path back to
|
|
16
|
+
* `head`. One missing file cost every edit made after it.
|
|
17
|
+
*
|
|
18
|
+
* So the order lives in one place and the patches reference nothing. A patch
|
|
19
|
+
* directory is self-contained data; this file says what order the directories go
|
|
20
|
+
* in. Removing a line cannot orphan the lines below it, because there is nothing
|
|
21
|
+
* below it to orphan — the entry after a dropped one is simply next.
|
|
22
|
+
*
|
|
23
|
+
* ## Why text
|
|
24
|
+
*
|
|
25
|
+
* It is read by people during exactly the incidents that make it interesting, and
|
|
26
|
+
* `cat` should be enough. One entry per line, whitespace-separated, id first:
|
|
27
|
+
*
|
|
28
|
+
* ```
|
|
29
|
+
* val-patch-log v1
|
|
30
|
+
* 659b8cfa-065c-47d1-8d82-fc69a2ac72a9 2026-08-27T11:46:13.730Z /content/authors.val.ts
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* Position in the file IS the order. There is deliberately no sequence number:
|
|
34
|
+
* a number stored beside the thing it describes is a number that can disagree
|
|
35
|
+
* with it, and then something has to decide which one lies.
|
|
36
|
+
*
|
|
37
|
+
* The timestamp and path are there to make the file readable, not to be believed
|
|
38
|
+
* — `patch.json` owns those fields. Nothing here is a second copy of state that
|
|
39
|
+
* anything reads back.
|
|
40
|
+
*/
|
|
41
|
+
export declare const PATCH_LOG_FILE_NAME = "patches.log";
|
|
42
|
+
export type PatchLogEntry = {
|
|
43
|
+
patchId: PatchId;
|
|
44
|
+
createdAt: string;
|
|
45
|
+
path: ModuleFilePath;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Something that is wrong with the file but does not stop it being read.
|
|
49
|
+
*
|
|
50
|
+
* Reported rather than thrown, because a log that is 99% intact is worth reading
|
|
51
|
+
* and then repairing. Only a file that cannot be understood at all is fatal, and
|
|
52
|
+
* that is a `status` on the read result, not a problem in this list.
|
|
53
|
+
*/
|
|
54
|
+
export type PatchLogProblem = {
|
|
55
|
+
type: "missing-header";
|
|
56
|
+
/** What stood where the header should have been. */
|
|
57
|
+
firstLine: string;
|
|
58
|
+
} | {
|
|
59
|
+
type: "unsupported-version";
|
|
60
|
+
header: string;
|
|
61
|
+
} | {
|
|
62
|
+
type: "unparseable-line";
|
|
63
|
+
lineNumber: number;
|
|
64
|
+
line: string;
|
|
65
|
+
} | {
|
|
66
|
+
type: "duplicate-entry";
|
|
67
|
+
patchId: PatchId;
|
|
68
|
+
lineNumber: number;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* A final line with no newline after it: a write that did not finish.
|
|
72
|
+
*
|
|
73
|
+
* Discarded rather than guessed at, and only ever possible on the LAST line —
|
|
74
|
+
* appends are serialized by the patch lock, so no other writer can have got in
|
|
75
|
+
* behind an interrupted one.
|
|
76
|
+
*/
|
|
77
|
+
| {
|
|
78
|
+
type: "torn-final-line";
|
|
79
|
+
line: string;
|
|
80
|
+
};
|
|
81
|
+
export type ReadPatchLogResult = {
|
|
82
|
+
status: "ok";
|
|
83
|
+
entries: PatchLogEntry[];
|
|
84
|
+
problems: PatchLogProblem[];
|
|
85
|
+
}
|
|
86
|
+
/** No log file at all — an empty store, not a broken one. */
|
|
87
|
+
| {
|
|
88
|
+
status: "absent";
|
|
89
|
+
} | {
|
|
90
|
+
status: "unreadable";
|
|
91
|
+
message: string;
|
|
92
|
+
};
|
|
93
|
+
export declare function formatPatchLogLine(entry: PatchLogEntry): string;
|
|
94
|
+
/**
|
|
95
|
+
* Split on the first two runs of whitespace only: a module file path may contain
|
|
96
|
+
* spaces, and it is last precisely so that it can.
|
|
97
|
+
*/
|
|
98
|
+
export declare function parsePatchLogLine(line: string): PatchLogEntry | null;
|
|
99
|
+
export declare function serializePatchLog(entries: readonly PatchLogEntry[]): string;
|
|
100
|
+
export declare function readPatchLog(logFilePath: string): ReadPatchLogResult;
|
|
101
|
+
export declare function parsePatchLog(raw: string): ReadPatchLogResult;
|
|
102
|
+
/**
|
|
103
|
+
* Add one entry to the end of the log.
|
|
104
|
+
*
|
|
105
|
+
* A single `writeSync` on an append-only descriptor, then fsync: the whole line
|
|
106
|
+
* reaches the file or none of it does, and a reader that catches it mid-flight
|
|
107
|
+
* discards the partial last line rather than misreading it.
|
|
108
|
+
*
|
|
109
|
+
* Callers must hold the patch lock. That is what makes "the torn line can only be
|
|
110
|
+
* the last one" true, and it is why this does not try to be safe against
|
|
111
|
+
* concurrent appends on its own.
|
|
112
|
+
*/
|
|
113
|
+
export declare function appendPatchLogEntry(logFilePath: string, entry: PatchLogEntry): void;
|
|
114
|
+
/**
|
|
115
|
+
* Replace the log wholesale — used by delete and by repair.
|
|
116
|
+
*
|
|
117
|
+
* Write a sibling temp file, fsync it, then rename over the original: a rename is
|
|
118
|
+
* atomic, so a reader either sees the old log or the new one and never a
|
|
119
|
+
* half-rewritten file. In-place truncation would have a window where the log is
|
|
120
|
+
* short and the store looks like it lost patches.
|
|
121
|
+
*
|
|
122
|
+
* Callers must hold the patch lock.
|
|
123
|
+
*/
|
|
124
|
+
export declare function writePatchLogFile(logFilePath: string, entries: readonly PatchLogEntry[]): void;
|