@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
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { ModuleFilePath, PatchId } from "@valbuild/core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { AuthorId, BaseSha } from "./ValOps.js";
|
|
4
|
+
import { PatchLogEntry, PatchLogProblem } from "./patchLog.js";
|
|
5
|
+
/**
|
|
6
|
+
* The on-disk shape of the local-dev patch store.
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* .val/patches/
|
|
10
|
+
* patches.log the order, and the only place it lives
|
|
11
|
+
* patches.repair.log what repair has done, for the person who has to know
|
|
12
|
+
* <patchId>/patch.json one plain, self-contained directory per patch
|
|
13
|
+
* <patchId>/base.json written when the patch is published
|
|
14
|
+
* <patchId>/files/… binary payloads for this patch's file ops
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* A directory is named after the patch it holds, and a record references nothing
|
|
18
|
+
* outside itself. That is the whole design, and it is a direct answer to how the
|
|
19
|
+
* old layout failed: there, a directory was named after a record's PARENT, so
|
|
20
|
+
* reading the store meant following links, and one absent record silently cut off
|
|
21
|
+
* every patch written after it.
|
|
22
|
+
*
|
|
23
|
+
* The invariant this module exists to hold up is narrow and worth stating: **the
|
|
24
|
+
* announced set and the delivered set are the same array.** `getStat` and
|
|
25
|
+
* `fetchPatches` both come out of one `readPatchStore` call, so they cannot report
|
|
26
|
+
* different numbers - which is exactly what they did when this broke, announcing
|
|
27
|
+
* 410 patches and delivering 359 with no error in between.
|
|
28
|
+
*/
|
|
29
|
+
export declare const PATCH_REPAIR_LOG_FILE_NAME = "patches.repair.log";
|
|
30
|
+
export declare const FSPatch: z.ZodObject<{
|
|
31
|
+
path: z.ZodString & z.ZodType<ModuleFilePath, string, z.core.$ZodTypeInternals<ModuleFilePath, string>>;
|
|
32
|
+
patch: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
33
|
+
op: z.ZodLiteral<"add">;
|
|
34
|
+
path: z.ZodArray<z.ZodString>;
|
|
35
|
+
value: z.ZodType<import("@valbuild/core/patch").JSONValue, unknown, z.core.$ZodTypeInternals<import("@valbuild/core/patch").JSONValue, unknown>>;
|
|
36
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
37
|
+
op: z.ZodLiteral<"remove">;
|
|
38
|
+
path: z.ZodTuple<[z.ZodString], z.ZodString>;
|
|
39
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
40
|
+
op: z.ZodLiteral<"replace">;
|
|
41
|
+
path: z.ZodArray<z.ZodString>;
|
|
42
|
+
value: z.ZodType<import("@valbuild/core/patch").JSONValue, unknown, z.core.$ZodTypeInternals<import("@valbuild/core/patch").JSONValue, unknown>>;
|
|
43
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
44
|
+
op: z.ZodLiteral<"move">;
|
|
45
|
+
from: z.ZodTuple<[z.ZodString], z.ZodString>;
|
|
46
|
+
path: z.ZodArray<z.ZodString>;
|
|
47
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
48
|
+
op: z.ZodLiteral<"copy">;
|
|
49
|
+
from: z.ZodArray<z.ZodString>;
|
|
50
|
+
path: z.ZodArray<z.ZodString>;
|
|
51
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
52
|
+
op: z.ZodLiteral<"test">;
|
|
53
|
+
path: z.ZodArray<z.ZodString>;
|
|
54
|
+
value: z.ZodType<import("@valbuild/core/patch").JSONValue, unknown, z.core.$ZodTypeInternals<import("@valbuild/core/patch").JSONValue, unknown>>;
|
|
55
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
56
|
+
op: z.ZodLiteral<"file">;
|
|
57
|
+
path: z.ZodArray<z.ZodString>;
|
|
58
|
+
filePath: z.ZodString;
|
|
59
|
+
value: z.ZodType<import("@valbuild/core/patch").JSONValue, unknown, z.core.$ZodTypeInternals<import("@valbuild/core/patch").JSONValue, unknown>>;
|
|
60
|
+
remote: z.ZodBoolean;
|
|
61
|
+
nestedFilePath: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
62
|
+
metadata: z.ZodOptional<z.ZodType<import("@valbuild/core/patch").JSONValue, unknown, z.core.$ZodTypeInternals<import("@valbuild/core/patch").JSONValue, unknown>>>;
|
|
63
|
+
}, z.core.$strict>], "op">>;
|
|
64
|
+
patchId: z.ZodString;
|
|
65
|
+
baseSha: z.ZodString & z.ZodType<BaseSha, string, z.core.$ZodTypeInternals<BaseSha, string>>;
|
|
66
|
+
authorId: z.ZodNullable<z.ZodString & z.ZodType<AuthorId, string, z.core.$ZodTypeInternals<AuthorId, string>>>;
|
|
67
|
+
createdAt: z.ZodString;
|
|
68
|
+
coreVersion: z.ZodNullable<z.ZodString>;
|
|
69
|
+
sessionId: z.ZodNullable<z.ZodString>;
|
|
70
|
+
}, z.core.$strip>;
|
|
71
|
+
export type FSPatchRecord = z.infer<typeof FSPatch>;
|
|
72
|
+
export declare const FSPatchBase: z.ZodObject<{
|
|
73
|
+
baseSha: z.ZodString & z.ZodType<BaseSha, string, z.core.$ZodTypeInternals<BaseSha, string>>;
|
|
74
|
+
timestamp: z.ZodString;
|
|
75
|
+
}, z.core.$strip>;
|
|
76
|
+
export type FSPatchBaseRecord = z.infer<typeof FSPatchBase>;
|
|
77
|
+
export type PatchStoreEntry = {
|
|
78
|
+
patchId: PatchId;
|
|
79
|
+
record: FSPatchRecord;
|
|
80
|
+
/** Present once the patch has been published. */
|
|
81
|
+
base: FSPatchBaseRecord | null;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Something wrong with the store that a reader can see.
|
|
85
|
+
*
|
|
86
|
+
* Every one of these used to be invisible. Reporting them is the point: the
|
|
87
|
+
* failure that motivated this rewrite was not that the store broke, it was that
|
|
88
|
+
* breaking looked exactly like working.
|
|
89
|
+
*/
|
|
90
|
+
export type PatchStoreProblem = {
|
|
91
|
+
type: "log";
|
|
92
|
+
problem: PatchLogProblem;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* A directory that cannot be used as a patch: no record, a record that does
|
|
96
|
+
* not parse, or one whose `patchId` is not the directory it sits in.
|
|
97
|
+
*
|
|
98
|
+
* That last case is what a store from before this layout looks like - its
|
|
99
|
+
* directories are named after each record's PARENT - and it is deliberately
|
|
100
|
+
* not special-cased. There is nothing to recover: the order lived in links
|
|
101
|
+
* that are exactly what goes wrong, so an old store is read as a pile of
|
|
102
|
+
* unusable directories and removed like any other.
|
|
103
|
+
*
|
|
104
|
+
* This is the problem the person editing is told about, because it is the one
|
|
105
|
+
* where unpublished work disappears.
|
|
106
|
+
*/
|
|
107
|
+
| {
|
|
108
|
+
type: "unreadable-patch";
|
|
109
|
+
/** The directory name, which for a usable patch IS the patch id. */
|
|
110
|
+
name: string;
|
|
111
|
+
dir: string;
|
|
112
|
+
message: string;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* A perfectly good record the log does not name.
|
|
116
|
+
*
|
|
117
|
+
* The benign half of a crash: a record is written before its log line, so an
|
|
118
|
+
* interrupted append leaves this behind. Nothing ever read it, so nothing is
|
|
119
|
+
* lost by sweeping it up, and the person editing does not need to hear about
|
|
120
|
+
* a patch that never existed as far as they were concerned.
|
|
121
|
+
*/
|
|
122
|
+
| {
|
|
123
|
+
type: "orphan-directory";
|
|
124
|
+
name: string;
|
|
125
|
+
dir: string;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The log was gone, and the order was recovered from the records' timestamps.
|
|
129
|
+
*
|
|
130
|
+
* Reported because it is a guess. Patches written inside the same millisecond
|
|
131
|
+
* have no recoverable order - a real store had eight inside 20ms.
|
|
132
|
+
*/
|
|
133
|
+
| {
|
|
134
|
+
type: "reconstructed-log";
|
|
135
|
+
entryCount: number;
|
|
136
|
+
};
|
|
137
|
+
export type ReadPatchStoreResult = {
|
|
138
|
+
status: "ok";
|
|
139
|
+
entries: PatchStoreEntry[];
|
|
140
|
+
problems: PatchStoreProblem[];
|
|
141
|
+
} | {
|
|
142
|
+
status: "unreadable";
|
|
143
|
+
message: string;
|
|
144
|
+
};
|
|
145
|
+
export declare function patchesLogFile(patchesDir: string): string;
|
|
146
|
+
export declare function patchRepairLogFile(patchesDir: string): string;
|
|
147
|
+
export declare function patchDir(patchesDir: string, patchId: PatchId): string;
|
|
148
|
+
export declare function patchRecordFile(patchesDir: string, patchId: PatchId): string;
|
|
149
|
+
export declare function patchBaseFile(patchesDir: string, patchId: PatchId): string;
|
|
150
|
+
export declare function patchBinaryFile(patchesDir: string, patchId: PatchId, filePath: string): string;
|
|
151
|
+
export declare function patchBinaryFileMetadata(patchesDir: string, patchId: PatchId, filePath: string): string;
|
|
152
|
+
/**
|
|
153
|
+
* Read the whole store: the order, the records, and everything wrong with it.
|
|
154
|
+
*
|
|
155
|
+
* One call, one answer. Callers that need only the ids and callers that need the
|
|
156
|
+
* ops both use this, which is what stops them disagreeing.
|
|
157
|
+
*/
|
|
158
|
+
export declare function readPatchStore(patchesDir: string): ReadPatchStoreResult;
|
|
159
|
+
/** Write a patch record so that a reader sees all of it or none of it. */
|
|
160
|
+
export declare function writePatchRecord(patchesDir: string, patchId: PatchId, record: FSPatchRecord): void;
|
|
161
|
+
/**
|
|
162
|
+
* Add a patch to the store.
|
|
163
|
+
*
|
|
164
|
+
* Record first, then the log line, and the order is the safety property: an
|
|
165
|
+
* interrupted append leaves a directory nothing points at, which repair sweeps
|
|
166
|
+
* up. The reverse order would leave the log naming a patch that is not there -
|
|
167
|
+
* the exact state this whole rewrite exists to make unreachable.
|
|
168
|
+
*
|
|
169
|
+
* Callers must hold the patch lock.
|
|
170
|
+
*/
|
|
171
|
+
export declare function appendPatch(patchesDir: string, record: FSPatchRecord): PatchLogEntry;
|
|
172
|
+
export type RepairAction = {
|
|
173
|
+
type: "removed-unreadable-patch";
|
|
174
|
+
name: string;
|
|
175
|
+
because: string;
|
|
176
|
+
} | {
|
|
177
|
+
type: "removed-orphan-directory";
|
|
178
|
+
name: string;
|
|
179
|
+
} | {
|
|
180
|
+
type: "rewrote-log";
|
|
181
|
+
entryCount: number;
|
|
182
|
+
};
|
|
183
|
+
/**
|
|
184
|
+
* Bring the store back to a state where the log and the directories agree.
|
|
185
|
+
*
|
|
186
|
+
* Safe in a way the old layout's repair could never be: the log is a flat list,
|
|
187
|
+
* so dropping an entry does not orphan the entries after it. There is nothing to
|
|
188
|
+
* re-link, which is why this can run unattended where re-parenting a chain could
|
|
189
|
+
* not.
|
|
190
|
+
*
|
|
191
|
+
* A patch that cannot be read is removed rather than kept around to fail again
|
|
192
|
+
* on every load. That does discard unpublished work, which is why
|
|
193
|
+
* {@link RepairAction} carries the reason, why it is written to
|
|
194
|
+
* `patches.repair.log`, and why the caller is expected to tell the person
|
|
195
|
+
* editing.
|
|
196
|
+
*
|
|
197
|
+
* Callers must hold the patch lock.
|
|
198
|
+
*/
|
|
199
|
+
export declare function repairPatchStore(patchesDir: string, read: Extract<ReadPatchStoreResult, {
|
|
200
|
+
status: "ok";
|
|
201
|
+
}>): RepairAction[];
|
|
202
|
+
/**
|
|
203
|
+
* Last resort: move the whole store aside and start empty.
|
|
204
|
+
*
|
|
205
|
+
* A rename, never a delete. What is being given up on here is someone's
|
|
206
|
+
* unpublished work, and the least this can do is say where it went.
|
|
207
|
+
*
|
|
208
|
+
* Callers must hold the patch lock.
|
|
209
|
+
*/
|
|
210
|
+
export declare function resetPatchStore(patchesDir: string, reason: string): {
|
|
211
|
+
movedTo: string;
|
|
212
|
+
} | {
|
|
213
|
+
error: string;
|
|
214
|
+
};
|
|
215
|
+
/** One line per problem, for a log line or an API error a person has to act on. */
|
|
216
|
+
export declare function describePatchStoreProblems(problems: readonly PatchStoreProblem[]): string[];
|