@valbuild/server 0.147.0 → 0.148.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.
Files changed (29) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/browser/dist/valbuild-server-browser.cjs.dev.js +2 -2
  3. package/browser/dist/valbuild-server-browser.cjs.prod.js +2 -2
  4. package/browser/dist/valbuild-server-browser.esm.js +2 -2
  5. package/dist/{ValRouterCore-7ffcc08c.cjs.prod.js → ValRouterCore-42772c21.cjs.prod.js} +980 -53
  6. package/dist/{ValRouterCore-f5004f8f.esm.js → ValRouterCore-7ca3dc68.esm.js} +972 -54
  7. package/dist/{ValRouterCore-3fd3d444.cjs.dev.js → ValRouterCore-9bdaaf89.cjs.dev.js} +980 -53
  8. package/dist/declarations/src/ValOps.d.ts +32 -11
  9. package/dist/declarations/src/ValOpsMemory.d.ts +4 -4
  10. package/dist/declarations/src/ValServer.d.ts +1 -2
  11. package/dist/declarations/src/checkRemoteRef.d.ts +40 -2
  12. package/dist/declarations/src/createFixPatch.d.ts +25 -3
  13. package/dist/declarations/src/fixFiles.d.ts +42 -0
  14. package/dist/declarations/src/fixHandlers.d.ts +14 -0
  15. package/dist/declarations/src/getSettings.d.ts +1 -0
  16. package/dist/declarations/src/index.d.ts +1 -0
  17. package/dist/declarations/src/platform.d.ts +16 -0
  18. package/dist/declarations/src/remoteFileCheck.d.ts +116 -0
  19. package/dist/declarations/src/remoteHostCheck.d.ts +101 -0
  20. package/dist/declarations/src/studioValidation.d.ts +84 -0
  21. package/dist/declarations/src/uploadRemoteFile.d.ts +1 -1
  22. package/dist/declarations/src/videosetFixes.d.ts +3 -2
  23. package/dist/{getJsonEntryAtCommit-df65d861.cjs.prod.js → getJsonEntryAtCommit-2e36e805.cjs.prod.js} +3 -4
  24. package/dist/{getJsonEntryAtCommit-1e08d724.esm.js → getJsonEntryAtCommit-40b6d9c1.esm.js} +3 -4
  25. package/dist/{getJsonEntryAtCommit-e96beb9e.cjs.dev.js → getJsonEntryAtCommit-64f67a50.cjs.dev.js} +3 -4
  26. package/dist/valbuild-server.cjs.dev.js +1352 -695
  27. package/dist/valbuild-server.cjs.prod.js +1352 -695
  28. package/dist/valbuild-server.esm.js +1352 -696
  29. package/package.json +4 -5
@@ -1,10 +1,10 @@
1
- import { Buffer } from "buffer";
2
1
  import { MediaSource, FileMetadata, FileSource, ImageMetadata, ModuleFilePath, PatchId, Schema, SelectorSource, SerializedSchema, Source, SourcePath, ValConfig, ValModules, ValidationError } from "@valbuild/core";
3
2
  import { result } from "@valbuild/core/fp";
4
3
  import { JSONValue, ParentRef, Patch, PatchError } from "@valbuild/core/patch";
5
4
  import type { HistoryError } from "./history/HistoryError.js";
6
5
  import type { PlanRefusalErrorT } from "@valbuild/shared/internal";
7
6
  import type { AffectedFile, StoredModuleVersion, CommitPage, CommitPatch, HistoricalCommit } from "./history/types.js";
7
+ import { type RemoteFilesCheck, type RemoteNetwork } from "./remoteFileCheck.js";
8
8
  import type { ValSyntaxError, ValSyntaxErrorTree } from "./patch/ts/syntax.js";
9
9
  import { ParentPatchId } from "@valbuild/core";
10
10
  import type { ReifiedPreview } from "@valbuild/core";
@@ -383,7 +383,14 @@ export declare abstract class ValOps {
383
383
  * are absent. `/sources/~` overlays the two for exactly this reason.
384
384
  */
385
385
  getSourcesWithPatchesApplied(analysis: PatchAnalysis & OrderedPatches): Promise<Awaited<ReturnType<ValOps["getSources"]>>>;
386
- validateSources(schemas: Schemas, sources: Sources, patchesByModule?: PatchAnalysis["patchesByModule"]): Promise<{
386
+ validateSources(schemas: Schemas, sources: Sources, patchesByModule?: PatchAnalysis["patchesByModule"],
387
+ /**
388
+ * Validate only these modules. The snapshot the cross-module checks read
389
+ * (a keyOf's keys, a route, a set's directory) is still built from every
390
+ * module, which is what makes checking one module at a time give the same
391
+ * answer for it as checking them all. Wins over `patchesByModule`.
392
+ */
393
+ onlyModules?: readonly ModuleFilePath[]): Promise<{
387
394
  errors: Record<ModuleFilePath, {
388
395
  invalidSource?: {
389
396
  message: string;
@@ -393,7 +400,19 @@ export declare abstract class ValOps {
393
400
  files: Record<SourcePath, FileSource>;
394
401
  remoteFiles: Record<SourcePath, MediaSource>;
395
402
  }>;
396
- validateRemoteFiles(schemas: Schemas, sources: Sources, remoteFiles: Record<SourcePath, MediaSource>): Promise<Record<SourcePath, ValidationError[]>>;
403
+ /**
404
+ * The remote values `validateSources` set aside, checked: is each ref
405
+ * sound, and does its host serve it. See `checkRemoteFiles`.
406
+ *
407
+ * `fileLastUpdatedByPatchId` names the refs whose bytes are still in a
408
+ * pending patch, which the host will not have until publish.
409
+ */
410
+ validateRemoteFiles(schemas: Schemas, sources: Sources, remoteFiles: Record<SourcePath, MediaSource>, fileLastUpdatedByPatchId?: PatchAnalysis["fileLastUpdatedByPatchId"], network?: RemoteNetwork): Promise<RemoteFilesCheck>;
411
+ /**
412
+ * Is a remote file's bytes in a pending patch? `not-pending` when no patch
413
+ * wrote it; `missing` when one did and its bytes are not there.
414
+ */
415
+ pendingBytesOf(fileLastUpdatedByPatchId: PatchAnalysis["fileLastUpdatedByPatchId"]): (ref: string) => Promise<"present" | "missing" | "not-pending">;
397
416
  validateFiles(schemas: Schemas, sources: Sources, files: Record<SourcePath, FileSource>, fileLastUpdatedByPatchId?: PatchAnalysis["fileLastUpdatedByPatchId"]): Promise<Record<SourcePath, ValidationError[]>>;
398
417
  /**
399
418
  * Applies the pending patches to the source files so they can be committed.
@@ -641,13 +660,13 @@ export declare abstract class ValOps {
641
660
  * gets this for free; a backend that can take bytes straight through should
642
661
  * override it.
643
662
  */
644
- saveBinaryFileFromPatch(filePath: string, parentRef: ParentRef, patchId: PatchId, bytes: Buffer, mimeType: string, type: "file" | "image", metadata: MetadataOfType<"file" | "image"> | undefined): Promise<WithGenericError<{
663
+ saveBinaryFileFromPatch(filePath: string, parentRef: ParentRef, patchId: PatchId, bytes: Uint8Array, mimeType: string, type: "file" | "image", metadata: MetadataOfType<"file" | "image"> | undefined): Promise<WithGenericError<{
645
664
  patchId: PatchId;
646
665
  filePath: string;
647
666
  }>>;
648
- abstract getBase64EncodedBinaryFileFromPatch(filePath: string, patchId: PatchId, remote: boolean): Promise<Buffer | null>;
667
+ abstract getBase64EncodedBinaryFileFromPatch(filePath: string, patchId: PatchId, remote: boolean): Promise<Uint8Array | null>;
649
668
  protected abstract getBase64EncodedBinaryFileMetadataFromPatch<T extends "file" | "image">(filePath: string, type: T, patchId: PatchId, remote: boolean): Promise<OpsMetadata<T>>;
650
- abstract getBinaryFile(filePathOrRef: string): Promise<Buffer | null>;
669
+ abstract getBinaryFile(filePathOrRef: string): Promise<Uint8Array | null>;
651
670
  /**
652
671
  * A file's bytes as something a `Range` request can be answered from
653
672
  * without reading the rest: a video is seeked in by many small ranges, and
@@ -710,7 +729,7 @@ export declare abstract class ValOps {
710
729
  /** Which files the commit touched, and how. Names them; does not fetch them. */
711
730
  abstract getCommitAffectedFiles(commitSha: string): Promise<result.Result<AffectedFile[], HistoryError>>;
712
731
  /** One file's bytes as they were at one commit. */
713
- abstract getFileAtCommit(commitSha: string, filePath: string, remote: boolean): Promise<result.Result<Buffer, HistoryError>>;
732
+ abstract getFileAtCommit(commitSha: string, filePath: string, remote: boolean): Promise<result.Result<Uint8Array, HistoryError>>;
714
733
  /**
715
734
  * Where a module lives in the REPOSITORY, as `getFileAtCommit` wants it.
716
735
  *
@@ -1019,13 +1038,15 @@ export type OrderedPatchesMetadata = {
1019
1038
  networkError?: boolean;
1020
1039
  };
1021
1040
  export declare function getFieldsForType<T extends BinaryFileType>(type: T): (keyof MetadataOfType<T> & string)[];
1022
- export declare function createMetadataFromBuffer<T extends BinaryFileType>(type: BinaryFileType, mimeType: string, buffer: Buffer): OpsMetadata<T>;
1041
+ export declare function createMetadataFromBuffer<T extends BinaryFileType>(type: BinaryFileType, mimeType: string, buffer: Uint8Array): OpsMetadata<T>;
1023
1042
  export declare function guessMimeTypeFromPath(filePath: string): string | null;
1024
- export declare function bufferFromDataUrl(dataUrl: string): Buffer | undefined;
1043
+ export declare function bufferFromDataUrl(dataUrl: string,
1044
+ /** Node passes `Buffer`'s, which is faster: this is a whole upload. */
1045
+ decodeBase64?: (base64: string) => Uint8Array): Uint8Array | undefined;
1025
1046
  /** A file of known size whose bytes are read a range at a time. */
1026
1047
  export type BinaryFileReader = {
1027
1048
  readonly size: number;
1028
1049
  /** Bytes `start` to `end`, both inclusive, as an HTTP range is. */
1029
- read(start: number, end: number): Promise<Buffer>;
1050
+ read(start: number, end: number): Promise<Uint8Array>;
1030
1051
  };
1031
- export declare function bufferReader(buffer: Buffer): BinaryFileReader;
1052
+ export declare function bufferReader(buffer: Uint8Array): BinaryFileReader;
@@ -34,7 +34,7 @@ export type StoredFile = {
34
34
  * has resolved that before it asks.
35
35
  */
36
36
  filePath: string;
37
- data: Buffer;
37
+ data: Uint8Array;
38
38
  metadata: MetadataOfType<"file" | "image"> | undefined;
39
39
  };
40
40
  /**
@@ -294,9 +294,9 @@ export declare class ValOpsMemory extends ValOps {
294
294
  patchId: PatchId;
295
295
  filePath: string;
296
296
  }>>;
297
- getBase64EncodedBinaryFileFromPatch(filePath: string, patchId: PatchId, _remote?: boolean): Promise<Buffer | null>;
297
+ getBase64EncodedBinaryFileFromPatch(filePath: string, patchId: PatchId, _remote?: boolean): Promise<Uint8Array | null>;
298
298
  protected getBase64EncodedBinaryFileMetadataFromPatch<T extends "file" | "image">(filePath: string, type: T, patchId: PatchId, _remote?: boolean): Promise<OpsMetadata<T>>;
299
- getBinaryFile(_filePathOrRef: string): Promise<Buffer | null>;
299
+ getBinaryFile(_filePathOrRef: string): Promise<Uint8Array | null>;
300
300
  protected getBinaryFileMetadata<T extends "file" | "image">(_filePath: string, _type: T): Promise<OpsMetadata<T>>;
301
301
  listCommits(): Promise<result.Result<CommitPage, HistoryError>>;
302
302
  getCommitPatches(): Promise<result.Result<{
@@ -308,7 +308,7 @@ export declare class ValOpsMemory extends ValOps {
308
308
  complete: boolean;
309
309
  }, HistoryError>>;
310
310
  getCommitAffectedFiles(): Promise<result.Result<AffectedFile[], HistoryError>>;
311
- getFileAtCommit(): Promise<result.Result<Buffer, HistoryError>>;
311
+ getFileAtCommit(): Promise<result.Result<Uint8Array, HistoryError>>;
312
312
  gitPathOfModule(_moduleFilePath: ModuleFilePath): result.Result<string, HistoryError>;
313
313
  }
314
314
  /** Kept exported so a host can name the error shape it may get back. */
@@ -1,4 +1,3 @@
1
- import { Buffer } from "buffer";
2
1
  import type { ValContentVersion, ValProposal } from "./proposal.js";
3
2
  import { ValModules, ValConfig } from "@valbuild/core";
4
3
  import { Api, ServerOf } from "@valbuild/shared/internal";
@@ -197,7 +196,7 @@ export declare const ENABLE_COOKIE_VALUE: {
197
196
  readonly sameSite: "lax";
198
197
  };
199
198
  };
200
- export declare function bufferToReadableStream(buffer: Buffer): ReadableStream<any>;
199
+ export declare function bufferToReadableStream(buffer: Uint8Array): ReadableStream<any>;
201
200
  export declare function getRedirectUrl(query: {
202
201
  redirect_to?: string | undefined;
203
202
  }, overrideHost: string | undefined): string | {
@@ -1,5 +1,16 @@
1
1
  import { FileMetadata, ImageMetadata, SerializedFileSchema, SerializedImageSchema } from "@valbuild/core";
2
- export declare function checkRemoteRef(remoteHost: string, ref: string, projectRoot: string, schema: SerializedImageSchema | SerializedFileSchema, metadata: Record<string, unknown> | undefined): Promise<{
2
+ export declare function checkRemoteRef(
3
+ /**
4
+ * Unused: a re-derived ref keeps the host it already names (see below).
5
+ * Kept so the exported signature does not change.
6
+ */
7
+ _remoteHost: string, ref: string, projectRoot: string, schema: SerializedImageSchema | SerializedFileSchema, metadata: Record<string, unknown> | undefined,
8
+ /**
9
+ * Where the bytes come from when the ref has to be re-derived. Defaults to
10
+ * a download cached under `projectRoot`; the Studio passes one that keeps
11
+ * nothing on disk (`FixFiles.readRemoteFile`).
12
+ */
13
+ readRemote?: ReadRemoteFile): Promise<{
3
14
  status: "success";
4
15
  } | {
5
16
  status: "fix-required";
@@ -11,10 +22,37 @@ export declare function checkRemoteRef(remoteHost: string, ref: string, projectR
11
22
  }>;
12
23
  export declare function getCachedRemoteFileDir(projectRoot: string): string;
13
24
  export declare function getCachedRemoteFilePath(fileExt: string, currentFileHash: string, remoteFileCacheDir: string): string;
14
- export declare function downloadFileFromRemote(ref: string, filePath: string): Promise<{
25
+ /**
26
+ * A remote file's bytes, and the hash they actually have — which is the one a
27
+ * re-derived ref is made from, whatever hash the old ref claimed.
28
+ */
29
+ export type ReadRemoteFile = (ref: string, fileExt: string, expectedFileHash: string) => Promise<{
30
+ status: "success";
31
+ fileBuffer: Buffer;
32
+ fileHash: string;
33
+ /** Where a download was kept, when it was kept: for error messages. */
34
+ cachedFilePath?: string;
35
+ } | {
36
+ status: "error";
37
+ error: string;
38
+ }>;
39
+ export declare function getFileBufferFromRemote(ref: string, fileExt: string, currentFileHash: string, projectRoot: string): ReturnType<ReadRemoteFile>;
40
+ /**
41
+ * Download a remote file to `filePath`, from where its ref says -- and, for a
42
+ * platform ref whose host fails, from the same file on the project's own
43
+ * `valstart.dev` address, which always serves it (`fallbackRefOf`). The bytes
44
+ * from the fallback are checked against the ref's file hash before they are
45
+ * kept: a different host answering is only safe because of that check.
46
+ *
47
+ * Rejects with the FIRST host's error when both fail: that is the one the ref
48
+ * names, and the one a person can recognise.
49
+ */
50
+ export declare function downloadFileFromRemote(ref: string, filePath: string): ReturnType<typeof downloadFromUrl>;
51
+ declare function downloadFromUrl(ref: string, filePath: string): Promise<{
15
52
  status: "success";
16
53
  filePath: string;
17
54
  } | {
18
55
  status: "error";
19
56
  error: string;
20
57
  }>;
58
+ export {};
@@ -1,5 +1,7 @@
1
1
  import { FileMetadata, ImageMetadata, VideoMetadata, SerializedSchema, Source, SourcePath, ValidationError } from "@valbuild/core";
2
2
  import { JSONValue, Patch } from "@valbuild/core/patch";
3
+ import { type AskHostProject } from "./remoteHostCheck.js";
4
+ import { FixFiles } from "./fixFiles.js";
3
5
  export type FixPatchRemainingError = ValidationError & {
4
6
  sourcePath?: SourcePath;
5
7
  };
@@ -17,6 +19,16 @@ export declare function mediaValue(path: string, metadata: unknown): Record<stri
17
19
  export declare function createFixPatch(config: {
18
20
  projectRoot: string;
19
21
  remoteHost: string;
22
+ /**
23
+ * Where a fix reads a file's bytes and puts a downloaded one. Defaults to
24
+ * the disk under `projectRoot`; see {@link FixFiles}.
25
+ */
26
+ files?: FixFiles;
27
+ /**
28
+ * How a platform ref's host is asked which project it serves. Defaults
29
+ * to `GET <host>/_val/project`; tests replace it.
30
+ */
31
+ askHostProject?: AskHostProject;
20
32
  }, apply: boolean, sourcePath: SourcePath, validationError: ValidationError, remoteFiles: {
21
33
  [sourcePath: SourcePath]: {
22
34
  ref: string;
@@ -27,6 +39,16 @@ export declare function createFixPatch(config: {
27
39
  patch: Patch;
28
40
  remainingErrors: FixPatchRemainingError[];
29
41
  } | undefined>;
30
- export declare function getVideoMetadata(projectRoot: string, fileRef: string): Promise<VideoMetadata>;
31
- export declare function getImageMetadata(projectRoot: string, validationError: ValidationError): Promise<ImageMetadata>;
32
- export declare function getFileMetadata(projectRoot: string, validationError: ValidationError): Promise<FileMetadata>;
42
+ export declare function getVideoMetadata(files: FixFiles, fileRef: string): Promise<VideoMetadata>;
43
+ export declare function getImageMetadata(files: FixFiles, validationError: ValidationError): Promise<ImageMetadata>;
44
+ export declare function getFileMetadata(files: FixFiles, validationError: ValidationError): Promise<FileMetadata>;
45
+ /**
46
+ * What an image or file directory's entry records that its bytes disagree
47
+ * with, as `[field, recorded, actual]`. The one comparison: the CLI's
48
+ * `*:check-all-files` fix and the Studio's full check both ask it.
49
+ */
50
+ export declare function mediaDirEntryMismatches(type: "images" | "files", stored: Record<string, unknown>, actual: {
51
+ width?: number;
52
+ height?: number;
53
+ mimeType?: string;
54
+ }): [field: string, recorded: unknown, actual: unknown][];
@@ -0,0 +1,42 @@
1
+ import { VideoMetadata } from "@valbuild/core";
2
+ import { type ReadRemoteFile } from "./checkRemoteRef.js";
3
+ /**
4
+ * Where `createFixPatch` reads a project file's bytes, and where it puts a
5
+ * remote file it was asked to download.
6
+ *
7
+ * Every path is the file's path from the project root, the way a source names
8
+ * it: `/public/val/logo.png`, never an absolute path on some disk. That is the
9
+ * point: a project in http mode, or one the platform serves from a Worker, has
10
+ * no disk to join a path onto, and a fix that reads a file has to work there
11
+ * too. `diskFixFiles` is the one the CLI and the language server use, and does
12
+ * exactly what `createFixPatch` did with `fs` before this existed.
13
+ *
14
+ * Not yet covered, and still read from `projectRoot` directly: the CLI's
15
+ * listing of a media directory for files it does not track, and
16
+ * `videos:check-all-files` (`videosetCheckAllFilesPatch`), which lists one.
17
+ * See `docs/plans/studio-validate.md`.
18
+ */
19
+ export type FixFiles = {
20
+ /** A project file's bytes. Rejects when it cannot be read. */
21
+ readFile(filePath: string): Promise<Buffer>;
22
+ /**
23
+ * A project video's size, length and mime type. Its own method rather than
24
+ * `readFile` because a video is mostly frames, and the disk reader reads
25
+ * only the headers.
26
+ */
27
+ readVideoMetadata(filePath: string): Promise<VideoMetadata>;
28
+ /**
29
+ * A remote file's bytes, for re-deriving a stale ref (`*:check-remote`).
30
+ * Nothing is stored in the project.
31
+ */
32
+ readRemoteFile: ReadRemoteFile;
33
+ /** Downloads `url` and stores it in the project at `filePath`. */
34
+ saveRemoteFile(url: string, filePath: string): Promise<{
35
+ status: "success";
36
+ } | {
37
+ status: "error";
38
+ error: string;
39
+ }>;
40
+ };
41
+ /** {@link FixFiles} over a project on disk. */
42
+ export declare function diskFixFiles(projectRoot: string): FixFiles;
@@ -44,6 +44,8 @@ export type IValRemote = {
44
44
  remoteFileBuckets: {
45
45
  bucket: string;
46
46
  }[];
47
+ /** See `getSettings`: the host new refs name. */
48
+ remoteHost?: string;
47
49
  };
48
50
  } | {
49
51
  success: false;
@@ -134,12 +136,16 @@ export type ValidationEvent = {
134
136
  sourcePath: string;
135
137
  message: string;
136
138
  keyError?: boolean;
139
+ /** See `isRepairedOnSave`: the Studio would not hold a publish for it. */
140
+ repairedOnSave?: boolean;
137
141
  } | {
138
142
  type: "validation-fixable-error";
139
143
  sourcePath: string;
140
144
  message: string;
141
145
  fixable: boolean;
142
146
  keyError?: boolean;
147
+ /** See `isRepairedOnSave`: the Studio would not hold a publish for it. */
148
+ repairedOnSave?: boolean;
143
149
  } | {
144
150
  type: "unknown-fix";
145
151
  sourcePath: string;
@@ -246,6 +252,14 @@ export declare function handleJsonValuesExtractEntry(ctx: FixHandlerContext): Pr
246
252
  * exists to send it there, and to say what `--fix` would do when it is off.
247
253
  */
248
254
  export declare function handleViewCheckModule(ctx: FixHandlerContext): Promise<FixHandlerResult>;
255
+ /**
256
+ * `remote:rewrite-host`: a platform ref whose host no longer serves its
257
+ * project. The server's full check reports it, and so does `createFixPatch`'s
258
+ * `image:check-remote` branch, which is how `val validate` finds it: that
259
+ * branch asks the host first. Either way the patch is `createFixPatch`'s, and
260
+ * needs nothing but the ref -- the new host is derived from it.
261
+ */
262
+ export declare function handleRemoteRewriteHost(ctx: FixHandlerContext): Promise<FixHandlerResult>;
249
263
  export declare function handleExternalUpload(): Promise<FixHandlerResult>;
250
264
  export declare const currentFixHandlers: Record<Exclude<ValidationFix, "keyof:check-keys" | "image:check-gallery" | "file:check-gallery" | "video:check-gallery" | "router:check-route" | "locale:check-locale" | "router:check-locale-param" | "record:fill-keys">, FixHandler>;
251
265
  export declare const fixHandlers: Record<string, FixHandler>;
@@ -4,6 +4,7 @@ declare const SettingsSchema: z.ZodObject<{
4
4
  remoteFileBuckets: z.ZodArray<z.ZodObject<{
5
5
  bucket: z.ZodString;
6
6
  }, z.core.$strip>>;
7
+ remoteHost: z.ZodOptional<z.ZodString>;
7
8
  }, z.core.$strip>;
8
9
  type Settings = z.infer<typeof SettingsSchema>;
9
10
  export declare function getSettings(projectName: string, auth: {
@@ -19,6 +19,7 @@ export { formatSyntaxErrorTree } from "./patch/ts/syntax.js";
19
19
  export { analyzeValModule } from "./patch/ts/valModule.js";
20
20
  export type { ValModuleAnalysis } from "./patch/ts/valModule.js";
21
21
  export { createFixPatch } from "./createFixPatch.js";
22
+ export { askOncePerHost } from "./remoteHostCheck.js";
22
23
  export { fixHandlers, currentFixHandlers, createDefaultValFSHost, handleFileMetadata, handleRemoteFileUpload, handleRemoteGalleryFileUpload, handleRemoteFileDownload, handleRemoteFileCheck, handleUniqueFolderCheck, handleCheckAllFiles, handleJsonValuesExtractEntry, handleExternalUpload, } from "./fixHandlers.js";
23
24
  export { handleVideosetMetadata, handleVideosetUploadRemote, handleVideosetCheckRemote, handleVideosetCheckAllFiles, videosetEntryVideoSchema, filesOfVideosetEntry, } from "./videosetFixes.js";
24
25
  export type { FixHandler, ModulePatch, FixHandlerContext, FixHandlerResult, IValRemote, ValidationEvent, ValidationError, ValModule, } from "./fixHandlers.js";
@@ -6,6 +6,7 @@ import type { ValServerConfig } from "./ValServer.js";
6
6
  import type { ResolveRemoteFileAuthResult } from "./valServerConfig.js";
7
7
  import type { JwtResult } from "./jwt.js";
8
8
  import type { ProbeResult } from "./linkCheck/probeUrl.js";
9
+ import type { checkModuleForStudio, fixForStudio } from "./studioValidation.js";
9
10
  /**
10
11
  * What `ValServer` needs from the machine it runs on.
11
12
  *
@@ -32,8 +33,23 @@ export type ValServerPlatform = {
32
33
  encodeJwt: (payload: object, sessionKey: string) => string;
33
34
  getExpire: () => number;
34
35
  };
36
+ /**
37
+ * `bytes` as base64. For the paths that encode every file a publish commits:
38
+ * Node's `Buffer` is ten times faster than `btoa`, and a browser has only
39
+ * `btoa` (`bytesToBase64` in `bytes.ts`). Same answer either way.
40
+ */
41
+ bytesToBase64: (bytes: Uint8Array) => string;
35
42
  /** The developer's `val login` token. See `readValLoginToken`. */
36
43
  readValLoginToken: (options: ValServerConfig) => Promise<string | null>;
37
44
  /** Which credential talks to the content host. See `resolveRemoteFileAuth`. */
38
45
  resolveRemoteFileAuth: (options: ValServerConfig) => Promise<ResolveRemoteFileAuthResult>;
46
+ /**
47
+ * The Studio's full check (`/validate`, `/validate/fix`). It reads files'
48
+ * bytes and asks remote hosts, so it needs Node; absent in a browser, where
49
+ * both routes answer that it is not available.
50
+ */
51
+ studioValidation?: {
52
+ checkModuleForStudio: typeof checkModuleForStudio;
53
+ fixForStudio: typeof fixForStudio;
54
+ };
39
55
  };
@@ -0,0 +1,116 @@
1
+ import { MediaSource, SerializedSchema, SourcePath, ValidationError } from "@valbuild/core";
2
+ import { type AskHostProject } from "./remoteHostCheck.js";
3
+ /**
4
+ * Does the host a remote ref names still serve it?
5
+ *
6
+ * `found` and `missing` are the host's answer. `unreachable` is no answer at
7
+ * all — a timeout, a refused connection, a 5xx — and says nothing about the
8
+ * file, only about the host.
9
+ */
10
+ export type RemoteFileAnswer = {
11
+ status: "found";
12
+ } | {
13
+ status: "missing";
14
+ httpStatus: number;
15
+ } | {
16
+ status: "unreachable";
17
+ reason: string;
18
+ };
19
+ export type HeadRemoteFile = (url: string) => Promise<RemoteFileAnswer>;
20
+ /**
21
+ * How the remote check reaches the network: what it asks a host about a file,
22
+ * and which project a platform host serves. Both default to the real thing;
23
+ * tests replace them.
24
+ */
25
+ export type RemoteNetwork = {
26
+ head?: HeadRemoteFile;
27
+ askHostProject?: AskHostProject;
28
+ };
29
+ /**
30
+ * A HEAD, falling back to the first byte of a GET for a host that does not
31
+ * answer HEAD (405, 501): either way no file is downloaded to learn that it is
32
+ * there.
33
+ */
34
+ export declare const headRemoteFile: HeadRemoteFile;
35
+ /** For tests: forget every answer. */
36
+ export declare function clearRemoteFileAnswers(): void;
37
+ /** A host that did not serve the refs of a module it was asked about. */
38
+ export type RemoteHostProblem = {
39
+ /** `https://remote.val.build`, as the ref names it. */
40
+ host: string;
41
+ status: "missing" | "unreachable";
42
+ /** What the host answered, for "Details": never the row's sentence. */
43
+ details: string;
44
+ /** Every ref on that host in the module, checked or vouched for. */
45
+ sourcePaths: SourcePath[];
46
+ };
47
+ export type RemoteFilesCheck = {
48
+ /** Wrong refs, one by one: each has its own row. */
49
+ errors: Record<SourcePath, ValidationError[]>;
50
+ /** Hosts that failed: one row each, however many refs they cover. */
51
+ hosts: RemoteHostProblem[];
52
+ };
53
+ export type RemoteFilesCheckInput = {
54
+ /** What `validateSources` set aside as remote: every remote media value. */
55
+ remoteFiles: Record<SourcePath, MediaSource>;
56
+ /** The serialized schema at a path, or undefined when it does not resolve. */
57
+ schemaAt: (sourcePath: SourcePath) => SerializedSchema | undefined;
58
+ /**
59
+ * Does this ref have its bytes in a pending patch? They go to the remote
60
+ * host at publish, so until then asking the host for it is asking a
61
+ * question whose answer is "not yet".
62
+ */
63
+ pendingBytes: (ref: string) => Promise<"present" | "missing" | "not-pending">;
64
+ /**
65
+ * Is this value's ref the one already published at this path? Only those
66
+ * may be vouched for by another ref on the same host; see `sampleByHost`.
67
+ */
68
+ isPublished: (sourcePath: SourcePath, ref: string) => boolean;
69
+ /**
70
+ * Ask one published ref per host and take its answer for the rest.
71
+ *
72
+ * Right for a managed project, whose refs are only ever written by the
73
+ * Studio and whose host changes for every ref at once. Wrong for a connected
74
+ * one, where a developer can edit any single ref by hand. See
75
+ * `docs/plans/studio-validate.md`, "Remote refs in the full check".
76
+ */
77
+ sampleByHost: boolean;
78
+ /**
79
+ * Ask only whether the host serves each ref. For the keys of a media
80
+ * directory: what is recorded about the file is the entry, not a value at
81
+ * the path, and the ref's hash was made from a schema the directory
82
+ * synthesizes for it, so there is no schema here to judge it by.
83
+ */
84
+ existenceOnly?: boolean;
85
+ head?: HeadRemoteFile;
86
+ /** How a platform ref's host is asked which project it serves. */
87
+ askHostProject?: AskHostProject;
88
+ };
89
+ /**
90
+ * The remote half of the Studio's full check, for one module's remote values.
91
+ *
92
+ * Four questions, cheapest first, and a ref stops at the first one it fails:
93
+ *
94
+ * 1. **Is it a ref at all?** `splitRemoteRef` parses it.
95
+ * 2. **Does the ref agree with what is recorded about it?** A ref carries a
96
+ * hash of the schema's options, the file's extension and its width, height
97
+ * and mime type. When the value or the schema changed and the ref did not,
98
+ * the ref is stale, and **Fix** (`image:check-remote`) re-reads the file and
99
+ * writes the ref it should have. Computed with the ref's OWN core version:
100
+ * the CLI also re-derives every ref after a Val upgrade, and listing every
101
+ * remote image in the project after each upgrade would bury the rows that
102
+ * matter. Nothing here downloads.
103
+ * 3. **Does its host still serve its project?** Only for a platform ref
104
+ * (`…/_val/file/…`) that is not on the project's own `valstart.dev`
105
+ * address: one `GET <host>/_val/project` per host. A host that dropped the
106
+ * project gets a **Fix** (`remote:rewrite-host`) that points the ref at
107
+ * `<public id>.valstart.dev`. See `remoteHostCheck.ts`.
108
+ * 4. **Does the host have it?** A HEAD.
109
+ *
110
+ * Neither host question is asked of a ref whose bytes are in a pending patch,
111
+ * which the host will not have until publish: there the question is whether
112
+ * the patch still has them.
113
+ */
114
+ export declare function checkRemoteFiles(input: RemoteFilesCheckInput): Promise<RemoteFilesCheck>;
115
+ /** `www.acme.no`, from `https://www.acme.no`: how a person names a host. */
116
+ export declare function hostName(host: string): string;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Does the host a platform ref names still serve the project the ref names?
3
+ *
4
+ * A ref served by the platform loader names the project's host at the time
5
+ * the file was added: `https://www.acme.no/_val/file/p/3f9a2c1/…`. When the
6
+ * project drops `www.acme.no`, the bytes are still there, at
7
+ * `3f9a2c1.valstart.dev`, but every ref that names the old domain points at a
8
+ * host that no longer serves them. This is the question that finds those refs,
9
+ * and `rewriteRefHost` is what the fix (`remote:rewrite-host`) does about it.
10
+ * See valbuild/home `docs/remote-files-on-platform.md`, "Rewriting refs after a
11
+ * domain is removed", and `docs/plans/studio-validate.md`, step 6.
12
+ *
13
+ * Asked of the host itself (`GET <host>/_val/project`), not of content: the
14
+ * host's answer covers DNS, the certificate and the loader's mapping in one,
15
+ * it needs no credential, and it works for a ref that names a project other
16
+ * than this one -- a project seeded from a template has refs naming the
17
+ * template's.
18
+ */
19
+ /** The domain every platform project is always served at, by its public id. */
20
+ export declare const PLATFORM_SITES_DOMAIN = "valstart.dev";
21
+ /**
22
+ * The host every platform ref of `publicProjectId` can always be pointed at:
23
+ * `<public id>.valstart.dev` is permanent (decision 4 of the remote-files
24
+ * plan), and the loader serves a project's files there whatever else it has.
25
+ */
26
+ export declare function fallbackRemoteHost(publicProjectId: string): string;
27
+ /**
28
+ * Only a platform ref is asked about. `remote.val.build` is not a platform
29
+ * host and is not going away, and a ref already on the project's own
30
+ * `valstart.dev` address is on the fallback, which always serves it.
31
+ */
32
+ export declare function needsHostCheck(remoteHost: string, projectId: string): boolean;
33
+ /** `ref` with its host part replaced, and everything else as it was. */
34
+ export declare function rewriteRefHost(ref: string, remoteHost: string): string | null;
35
+ /**
36
+ * - `serves`: the host answered for the project the ref names.
37
+ * - `other-project`: it answered, for another one -- the domain was moved to
38
+ * a different project, and decision 5 means it will not serve these files.
39
+ * - `no-site`: it answered, but serves no project at all (the loader's 404 for
40
+ * a domain nobody has mapped any more).
41
+ * - `unreachable`: no answer. A domain whose DNS was removed looks like this,
42
+ * and so does a host that is down for a minute, which is why the row says
43
+ * which it saw.
44
+ */
45
+ export type HostServes = {
46
+ status: "serves";
47
+ } | {
48
+ status: "other-project";
49
+ publicProjectId: string;
50
+ } | {
51
+ status: "no-site";
52
+ httpStatus: number;
53
+ } | {
54
+ status: "unreachable";
55
+ reason: string;
56
+ };
57
+ export type AskHostProject = (remoteHost: string) => Promise<{
58
+ status: "answered";
59
+ publicProjectId: string;
60
+ } | {
61
+ status: "no-site";
62
+ httpStatus: number;
63
+ } | {
64
+ status: "unreachable";
65
+ reason: string;
66
+ }>;
67
+ /** `GET <host>/_val/project`, answered by the platform loader. */
68
+ export declare const askHostProject: AskHostProject;
69
+ export declare function clearHostAnswers(): void;
70
+ export declare function hostServes(remoteHost: string, projectId: string, ask?: AskHostProject): Promise<HostServes>;
71
+ /**
72
+ * `ask`, asking each host once for as long as the returned function is kept:
73
+ * one check, not the life of the server. A check walks its refs in order, so
74
+ * without it every ref on a host that does not answer waits out its own
75
+ * timeout -- 150 images on a dead domain is 20 minutes. Kept per check rather
76
+ * than in {@link hostServes}'s cache because a host that did not answer has
77
+ * to be asked again by the next one.
78
+ */
79
+ export declare function askOncePerHost(ask?: AskHostProject): AskHostProject;
80
+ /** `www.acme.no`, from `https://www.acme.no/_val`: how a person names a host. */
81
+ export declare function hostNameOfRemoteHost(remoteHost: string): string;
82
+ /**
83
+ * Where a platform ref has to move to, when its host no longer serves the
84
+ * project it names; `undefined` when it does not have to (or is not a
85
+ * platform ref). `why` and `to` are for the caller's sentence: what can be
86
+ * done about it differs by caller (a Studio **Fix**, `val validate --fix`, or
87
+ * nothing yet).
88
+ */
89
+ export declare function movedRemoteHost(ref: string, ask?: AskHostProject): Promise<{
90
+ ref: string;
91
+ why: string;
92
+ to: string;
93
+ } | undefined>;
94
+ /**
95
+ * Where else a ref's file can be downloaded from: the same file on the
96
+ * project's own `valstart.dev` address, for a platform ref on any other host.
97
+ * `null` when there is nowhere else -- a `remote.val.build` ref, or one already
98
+ * on the fallback. See valbuild/home `docs/remote-files-on-platform.md`,
99
+ * "Tooling: the fallback".
100
+ */
101
+ export declare function fallbackRefOf(ref: string): string | null;