import { MediaSource, FileMetadata, FileSource, ImageMetadata, ModuleFilePath, PatchId, Schema, SelectorSource, SerializedSchema, Source, SourcePath, ValConfig, ValModules, ValidationError } from "@valbuild/core"; import { result } from "@valbuild/core/fp"; import { JSONValue, ParentRef, Patch, PatchError } from "@valbuild/core/patch"; import type { HistoryError } from "./history/HistoryError.js"; import type { AffectedFile, StoredModuleVersion, CommitPage, CommitPatch, HistoricalCommit } from "./history/types.js"; import { ValSyntaxError, ValSyntaxErrorTree } from "./patch/ts/syntax.js"; import { ParentPatchId } from "@valbuild/core"; import type { ReifiedPreview } from "@valbuild/core"; import { ValCommit, ValDeployment } from "@valbuild/shared/internal"; export type BaseSha = string & { readonly _tag: unique symbol; }; export type ConfigSha = string & { readonly _tag: unique symbol; }; export type SourcesSha = string & { readonly _tag: unique symbol; }; export type SchemaSha = string & { readonly _tag: unique symbol; }; export type CommitSha = string & { readonly _tag: unique symbol; }; export type AuthorId = string & { readonly _tag: unique symbol; }; export type ModulesError = { message: string; path?: ModuleFilePath; }; export type Schemas = { [key: ModuleFilePath]: Schema; }; export type Sources = { [key: ModuleFilePath]: Source; }; export type ValOpsOptions = { formatter?: (code: string, filePath: string) => string | Promise; statPollingInterval?: number; statFilePollingInterval?: number; disableFilePolling?: boolean; disableFileWatcher?: boolean; config: ValConfig; }; export declare abstract class ValOps { private readonly valModules; protected readonly options?: ValOpsOptions | undefined; /** Sources from val modules, immutable (without patches or anything) */ private sources; /** The sha256 / hash of all sources + all schemas + config */ private baseSha; /** The sha256 / hash of all sources */ private sourcesSha; /** The sha256 / hash of config */ private configSha; /** Schema from val modules, immutable */ private schemas; /** The sha256 / hash of schema + config - if this changes users needs to reload */ private schemaSha; private modulesErrors; /** * What the SHAs above are a fold over, so they can be recomputed. * * See {@link promoteCommittedSources}: the one thing that changes sources * without re-evaluating the modules is a save, and it has to be able to move * the SHAs with them. */ private shaEntries; /** * The extraction's OWN module errors, which are what the fold was given. * * Not the same list as {@link modulesErrors}: that one has the nested * `.jsonValues()` errors concatenated on, and those were never part of the * hash. Re-folding with the wrong list changes the base SHA for no reason. */ private shaModuleErrors; /** * What a save has told us each `.jsonValues()` entry now holds. * * The entry twin of {@link sources}, and it has to be separate because an * entry's content is not IN the source: the source holds a marker, and * {@link getJsonEntries} resolves it by awaiting the marker's own `import()`. * That resolves from the module registry, so after `/save` rewrites a * `*.val.json` the thunk keeps answering with the content from before — and * unlike a module source there is nothing to re-extract, because the memo was * never holding the content in the first place. * * `null` for an entry the commit deleted. * * Never cleared: it describes what is on disk. A host rebuild makes a new * instance, which is the right reset. Bounded by the project's entry count, * holding only the latest content per key. */ private adoptedJsonEntries; constructor(valModules: ValModules, options?: ValOpsOptions | undefined); /** * Get the status from Val * * This works differently in ValOpsFS and ValOpsHttp: * - In ValOpsFS (for dev mode) works using long-polling operations since we cannot use WebSockets in the host Next.js server and we do not want to hammer the server with requests (though we could argue that it would be ok in dev, it is not up to our standards as a kick-ass CMS). * - In ValOpsHttp (in production) it returns a WebSocket URL so that the client can connect directly. * * The reason we do not use long polling in production is that Vercel (a very likely host for Next.js), bills by wall time and long polling would therefore be very expensive. */ abstract getStat(params: { baseSha: BaseSha; schemaSha: SchemaSha; patches?: PatchId[]; profileId?: AuthorId; /** * FS mode only (see ValOpsFS): the fingerprint of the `.jsonValues()` entry * FILES the client last saw. Absent in http mode, where content does not * change under a running server — a deploy restarts it. */ jsonEntriesSha?: string; } | null): Promise<{ type: "request-again" | "no-change" | "did-change"; baseSha: BaseSha; schemaSha: SchemaSha; sourcesSha: SourcesSha; patches: PatchId[]; /** * Unpublished changes the store threw away because it could not read * them. FS mode only: the content api owns its own patches and does not * discard them behind the client's back. */ removed?: { patchId: PatchId; reason: string; }[]; /** FS mode only — see the `params` counterpart. */ jsonEntriesSha?: string; } | { type: "use-websocket"; url: string; nonce: string; baseSha: BaseSha; schemaSha: SchemaSha; /** Absent for a project with no repository. See `git` on ValApiOptions. */ commitSha?: CommitSha; sourcesSha: SourcesSha; patches: PatchId[]; } | { type: "error"; error: GenericErrorMessage; unauthorized?: boolean; networkError?: boolean; }>; private initSources; /** * These patches are on disk now: adopt what they produced as the committed * sources. * * The entry point for the mechanism {@link promoteCommittedSources} describes, * and the only one — a caller hands over the analysis it just committed and * this works out the rest, so the rule about which sources are adopted lives * in one place rather than at each save site. * * A module whose patches could not be applied cleanly is left alone. `/save` * refuses the whole commit before reaching here if `prepare` found errors, so * this cannot normally fire — but adopting a partially patched source would * put content in the memo that is not what was written, which is worse than * being stale. */ adoptCommittedSources(analysis: PatchAnalysis & OrderedPatches, preparedCommit: Pick): Promise; /** * Adopt sources that have just been written to disk, and move the SHAs with * them. * * ## Why this exists rather than an invalidation * * The obvious thing — throw the memo away after a save so the next read * re-extracts — does not work, and quietly. `extractValModules` gets a * module's content by awaiting its `def`, which is the app's own `import()`: * that resolves from the MODULE REGISTRY, not from the file on disk. Right * after `/save` rewrites a `.val.ts`, the registry still holds the module as * it was evaluated before, so a re-extraction returns the pre-save content and * stores it as fresh. What actually replaces it is the host rebuilding its * module graph and constructing a new `ValOps` — which happens on its own * schedule, and until it does, every read is stale. * * Stale reads here are not abstract: `getJsonEntry` resolves a * `.jsonValues()` entry from the committed source and then replays pending * patches over it, so once a publish has removed the patches, a page rendering * draft content gets the committed value — the one this memo is holding from * before the publish. * * So the save tells us instead. It has just computed what the new committed * sources are, and that answer does not depend on anything being * re-evaluated. * * ## And the SHAs move * * Deliberately, and this is the part with consequences. `baseSha` and * `sourcesSha` identify the sources being served; leaving them still while the * sources move would put a value other code compares against into * disagreement with what it describes. Moving them means a `fs`-mode base SHA * changes within a server's lifetime for the first time, which is a signal the * studio already knows how to read: `PatchStore.reconcileVanished` uses a * moved base to tell "these patches were published" from "these patches were * discarded", and takes them out of the chain without reverting the fields — * which is what a second tab watching a publish needs and could not get * before. * * A module the fold does not know is ignored rather than appended: the fold's * order is `val.modules`, and a path that is not in it has no position, so * there is no honest answer for where its hash would go. It also cannot happen * — a save only ever writes modules it read from here. */ protected promoteCommittedSources(patched: Sources): void; init(): Promise; getBaseSources(): Promise; /** * Resolves the content of ONE `.jsonValues()` entry. * * The committed content comes from the entry's import thunk on the base * source (so it works in both fs and http mode, with no extra I/O). With * `applyPatches` (the default) any pending patches for that entry are then * replayed on top, which is what makes draft edits visible to the runtime. * * Callers that apply patches themselves (the Studio, which owns * in-flight client patches the server has not seen) must pass * `applyPatches: false` or the same edits would be applied twice. */ getJsonEntry(moduleFilePath: ModuleFilePath, entryKey: string, /** Passed straight through — see {@link getJsonEntries}. */ opts?: { applyPatches?: boolean; patchIds?: PatchId[]; }): Promise<{ status: "success"; content: JSONValue | null; } | { status: "not-found"; message: string; } | { status: "error"; message: string; } | { status: "unauthorized"; message: string; }>; /** * Resolves the content of MANY `.jsonValues()` entries in one pass. * * This is the single implementation; {@link getJsonEntry} is a one-key wrapper. * Batching matters because the expensive parts — `initSources()` and * `fetchPatches()` — are hoisted OUT of the per-entry loop: resolving 500 * entries one-by-one would otherwise mean 500 patch fetches. * * Per-entry problems stay per-entry (`missing` / `errors`) so one corrupt * `*.val.json` cannot fail a whole batch. Only a missing or non-record MODULE * is a whole-request `not-found`. * * `selector` is either explicit `keys` or an `offset`/`limit` window over every * key of the record, in module key order. The window form requires * `applyPatches: false`: enumerating from the base source would silently omit * draft-added keys, and a silently-short key list is exactly the class of bug * this endpoint exists to avoid. */ getJsonEntries(moduleFilePath: ModuleFilePath, selector: { keys: string[]; } | { offset: number; limit: number; }, opts?: { applyPatches?: boolean; /** * Only these pending patches, or every one when `undefined`. * * A draft render is scoped to the caller's own groups, and a page renders * `jsonValues` entries beside module content — so without this the two * halves of one page disagreed about whose unpublished work they showed. * `undefined` is what every other caller passes and must keep getting. */ patchIds?: PatchId[]; }): Promise<{ status: "success"; entries: { key: string; content: JSONValue | null; }[]; missing: string[]; errors: { key: string; message: string; }[]; total: number; offset?: number; limit?: number; } | { status: "not-found"; message: string; } | { status: "error"; message: string; } | { status: "unauthorized"; message: string; }>; getSchemas(): Promise; getSerializedSchemas(): Promise>; getModuleErrors(): Promise; getBaseSha(): Promise; getConfigSha(): Promise; getSourcesSha(): Promise; getSchemaSha(): Promise; analyzePatches(sortedPatches: OrderedPatches["patches"], commits?: ValCommit[], currentCommitSha?: CommitSha): PatchAnalysis; /** * Reifies each module's previews from its schema INSTANCE. * * Kept even though the Studio also computes previews client-side: a preview is * a user function that lives on the instance and is not part of the serialized * schema, so a host app that does not render `` has no * instances in the browser and would otherwise get no previews at all. See * #470. * * A `render`, and a string's `multiline`, need none of this — they are static * config that travels with the serialized schema. */ getPreviews(schemas: Schemas, sources: Sources): Promise<{ previews: Record; }>; getSources(analysis?: PatchAnalysis & OrderedPatches): Promise<{ sources: Sources; errors: Record; }>; /** * Every module's source, with the pending patches applied. * * `getSources(analysis)` returns ONLY the modules that had patches, which is * not enough to validate with: cross-module checks (keyOf, router routes) * resolve against other modules' sources and report spurious errors when they * are absent. `/sources/~` overlays the two for exactly this reason. */ getSourcesWithPatchesApplied(analysis: PatchAnalysis & OrderedPatches): Promise>>; validateSources(schemas: Schemas, sources: Sources, patchesByModule?: PatchAnalysis["patchesByModule"]): Promise<{ errors: Record; }>; files: Record; remoteFiles: Record; }>; validateRemoteFiles(schemas: Schemas, sources: Sources, remoteFiles: Record): Promise>; validateFiles(schemas: Schemas, sources: Sources, files: Record, fileLastUpdatedByPatchId?: PatchAnalysis["fileLastUpdatedByPatchId"]): Promise>; /** * Applies the pending patches to the source files so they can be committed. * * @param options.continueOnError Diagnosis only. By default a patch that * cannot be applied aborts the rest of that module's chain, which is what * /save requires: the commit is refused and nothing is written. With this * flag the failing patch is recorded in `unappliablePatches` and the chain * continues on the unchanged source file, so a single run reports *every* * unappliable patch instead of only the first one per module. The commit is * still refused (`hasErrors` stays true) - this only makes the report * complete. */ prepare(patchAnalysis: PatchAnalysis & OrderedPatches, options?: { continueOnError?: boolean; }): Promise; /** * Reads a project file as text at whatever revision this ops instance points * at: the deployed commit in http mode, the working tree in fs mode. * * Public counterpart of `getSourceFile`, for the CLI's debug snapshot. The * snapshot has to capture the exact text `prepare` patches, which in http mode * is NOT the local working copy. */ readProjectFile(path: string): Promise>; createPatch(path: ModuleFilePath, patch: Patch, patchId: PatchId, parentRef: ParentRef, sessionId: string | null, authorId: AuthorId | null, /** * Which patch group this patch joins, recorded in the SAME request. * * Atomic on purpose. The content API runs every refusal before its insert, * so an invalid closure is a 400 with nothing written. Recording membership * in a second call would let a patch exist outside its author's group if * that call failed — and a patch outside your own group is one you cannot * publish until a repair puts it back. * * Optional: `fs` mode has no groups, and a client that predates them sends * nothing. */ patchGroup?: PatchGroupMembership): Promise>; /** * Why a publish cannot happen here, or `null` when one can. * * Named rather than thrown, and asked BEFORE the click: the Studio shows the * reason and disables the action, instead of letting someone write a commit * message and then meeting a failure from four layers down. * * `no-base` is the only code so far and means what it says: there is nowhere * for this publish's commit to be based. Note which way round that is -- * a project whose content service is the store of record always HAS a base * (the service's own chain, which mints its own shas), so the refusal is not * about missing git. It is about a deployment that cannot do what its * project requires. */ publishRefusal(): PublishRefusal | null; /** * Whether a commit here produces `.val.ts` TEXT as well as data. * * True everywhere there is somewhere to put it: a working tree in `fs` mode, * a host holding its own source in memory mode, a git repository in `http` * mode. False for an `http` project whose content service is the store of * record and which has no repository attached -- see `git` on * {@link ValApiOptions}. * * WHAT IS NOT AFFECTED, and it is the part worth being sure of: * `moduleVersions` -- what each changed module IS after the commit, with its * schema -- comes from `getSources(analysis)`, which applies the ops to * Source in the stores. It does not go near the file text. So a commit with * no mirror still records everything history and a later `connect-github` * fold need; what it does not record is a rendering of that data as code. * * WHAT IS: the ops are no longer applied to the file text as well, so a * patch that would not fit the `.val.ts` is not reported here. That check * only ever existed for the text being produced, and there is none. */ protected readonly mirrorsSourceFiles: boolean; /** * Take the `.val.ts` text a commit produced as the new committed source. * * A no-op where {@link getSourceFile} reads something the commit already * wrote — the disk in `fs` mode, the content service in `http` mode. Override * it in a store that holds the source itself. `null` means the commit deleted * the file. */ protected adoptPatchedSourceFiles(_files: Record): void; /** * Whether the patches live HERE, in this server, or in Val's content service. * * Almost everything the routes branch on comes from this one fact, which is * why it is a named property rather than an `instanceof`. If this server owns * the store then there is no content service to authenticate to (so an absent * or unverifiable session is anonymous rather than a 401), no shared store for * a patch group to separate authors in, no deployments to report, and a * "publish" writes what the host does with it rather than pushing a commit. If * it does not, every one of those is the content service's and this server is * relaying. * * There were two implementations when the routes were written and `instanceof * ValOpsFS` meant this; a third made that reading wrong in a way that compiles * silently -- a store that is local, answers none of the checks, and gets the * http path with no content service behind it. * * It is also what `/stat` reports as `mode`. The wire name predates the third * implementation and names a class, but the question the client is asking is * this one: does it auto-save and hide the account panel, or does it publish. */ abstract readonly patchesAreLocal: boolean; /** * Whether a request must carry a session this server verified. * * Split out of {@link patchesAreLocal}, which was answering two questions at * once. "Does this store auto-save or publish" is a BEHAVIOUR question, and * it is what `/stat` reports and the UI keys off. "May an unauthenticated * request write here" is a SECURITY question. With two implementations the * answers coincided -- fs is local dev where no credential exists, http is * remote -- so one flag served both and nothing noticed. * * A third implementation splits them. `ValOpsMemory`'s store is local, which * makes the first answer yes, and it is designed to run DEPLOYED, which makes * the second answer no. Reusing one flag gave a deployed host `getAuth` * returning anonymous success for a missing cookie, an invalid JWT, an * unparseable payload, or no configured secret -- on all 29 routes, including * the ones that create patches and publish. */ abstract readonly requiresAuth: boolean; abstract onInit(baseSha: BaseSha, schemaSha: SchemaSha): Promise; abstract fetchPatches(filters: { patchIds?: PatchId[]; excludePatchOps: ExcludePatchOps; }): Promise; protected abstract saveSourceFilePatch(path: ModuleFilePath, patch: Patch, patchId: PatchId, parentRef: ParentRef | null, authorId: AuthorId | null, sessionId: string | null, patchGroup?: PatchGroupMembership): Promise; protected abstract getSourceFile(path: string): Promise>; /** * Save a patch's binary file from a `data:...;base64,...` URL. * * The wire form: `FileReader.readAsDataURL` is what the browser produces, and * published `@valbuild/server` versions send it. Code that already HAS bytes * should call {@link saveBinaryFileFromPatch} instead of wrapping them in a * data URL just to have this unwrap them again. * * A `null` `data` records a DELETION, which is why this cannot simply be * replaced by the byte-taking sibling: there is nothing to hand it. */ abstract saveBase64EncodedBinaryFileFromPatch(filePath: string, parentRef: ParentRef, patchId: PatchId, data: string | null, type: "file" | "image", metadata: MetadataOfType<"file" | "image"> | undefined): Promise>; /** * The same, for a caller that already has the bytes. * * Default implementation wraps them back into a data URL so every backend * gets this for free; a backend that can take bytes straight through should * override it. */ saveBinaryFileFromPatch(filePath: string, parentRef: ParentRef, patchId: PatchId, bytes: Buffer, mimeType: string, type: "file" | "image", metadata: MetadataOfType<"file" | "image"> | undefined): Promise>; abstract getBase64EncodedBinaryFileFromPatch(filePath: string, patchId: PatchId, remote: boolean): Promise; protected abstract getBase64EncodedBinaryFileMetadataFromPatch(filePath: string, type: T, patchId: PatchId, remote: boolean): Promise>; abstract getBinaryFile(filePathOrRef: string): Promise; protected abstract getBinaryFileMetadata(filePath: string, type: T): Promise>; abstract deletePatches(patchIds: PatchId[]): Promise<{ deleted: PatchId[]; errors?: undefined; error?: undefined; } | { deleted: PatchId[]; errors: Record; } | { error: GenericErrorMessage; errors?: undefined; deleted?: undefined; }>; /** One page of a branch's commits, newest first. See history/listCommits. */ abstract listCommits(branch: string, options?: { limit?: number; cursor?: string; }): Promise>; /** The patches that produced one commit, with their ops. */ abstract getCommitPatches(commitSha: string): Promise>; /** * How each `.val.ts` the commit changed looked BEFORE it, keyed by module * file path. Empty for a commit made before this was recorded - which the * caller reports as `source-unavailable` rather than as an empty module. */ /** * Each module a commit changed: its data, and the schema it was under. * * `asOf` widens it from "what this commit changed" to "the whole project as * this commit left it", which is what reverting everything to a point in time * needs; `moduleFilePath` narrows it to one module, for navigating the * history pane off the changed set. */ abstract getCommitModules(commitSha: string, options?: { asOf?: boolean; moduleFilePath?: ModuleFilePath; }): Promise>; /** Which files the commit touched, and how. Names them; does not fetch them. */ abstract getCommitAffectedFiles(commitSha: string): Promise>; /** One file's bytes as they were at one commit. */ abstract getFileAtCommit(commitSha: string, filePath: string, remote: boolean): Promise>; /** * Where a module lives in the REPOSITORY, as `getFileAtCommit` wants it. * * A `ModuleFilePath` is project-relative (`/app/page.val.ts`); a git path is * repository-relative and carries the project root in front of it * (`examples/next/app/page.val.ts`). Only the ops know that root, which is * why this is here rather than computed by the history functions - and why a * history function that needs to read a module's own file at a commit has to * ask instead of concatenating. */ abstract gitPathOfModule(moduleFilePath: ModuleFilePath): result.Result; } export type WithGenericError> = (T & { error?: undefined; }) | GenericError; export type GenericError = { error: { message: string; }; }; export type GenericErrorMessage = { message: string; details?: unknown; }; /** * The patch group a newly created patch joins. * * `withPatchIds` is the CLOSURE the client computed — the patches that share * a patch set with this one and must move with it. It is not derived here and * must not be: the closure needs the content schema, and the service that * stores groups does not have it. One implementation of that rule, on the side * that can actually compute it. * * Membership rows are stamped with `coreVersion` on the content side, the same * stamp the patch row itself carries, so which client wrote a row stays legible * after the fact. */ export type PatchGroupMembership = { /** * Absent means "the author's open group, created if absent" — the content API * resolves it. The client does not hold an id across publishes, because a * published group is refused and the stale id would lose the write. */ patchGroupId?: string; withPatchIds: PatchId[]; }; export type SaveSourceFilePatchResult = result.Result<{ patchId: PatchId; /** * The group the patch ended up in, where the store has groups at all. * * Absent in `fs` mode and against a content API that predates groups. The * client uses it to learn the id of the group its own first write created. */ patchGroupId?: string; }, ({ errorType: "other"; } & GenericErrorMessage) | { errorType: "patch-head-conflict"; }>; export type PatchAnalysis = { patchesByModule: { [path: ModuleFilePath]: { patchId: PatchId; }[]; }; fileLastUpdatedByPatchId: Record; }; export type PatchSourceError = { message: string; filePath?: string; } | PatchError | ValSyntaxError | ValSyntaxErrorTree; export declare function formatPatchSourceError(error: PatchSourceError): string; export type MetadataOfType = T extends "image" ? Omit : FileMetadata; export type OpsMetadata = { metadata: MetadataOfType; errors?: undefined; } | { errors: ((GenericErrorMessage & { field: string; }) | (GenericErrorMessage & { filePath?: string; }))[]; }; export type BinaryFileType = "file" | "image"; /** * Why a publish is refused, in a form a person can be shown. * * `code` is for the Studio to branch on and `message` is what it says. Both, * rather than a code and a lookup table on the client: the server knows what * is actually missing -- which branch, which commit -- and a client-side table * could only ever say the generic version. */ export type PublishRefusal = { code: "no-base"; message: string; }; export type PreparedCommit = { /** * Updated / new source files that are ready to be committed / saved. * A null value signals that the file at that path should be deleted. */ patchedSourceFiles: Record; /** * The committed content of every `.jsonValues()` entry this commit changed, * per module and entry key. `null` means the entry was deleted. * * Separate from {@link patchedSourceFiles} rather than folded into it, because * that map is keyed by FILE PATH and a reader of an entry has a module and a * key. A marker does not carry its path at read time, so the two are not * interchangeable — see `jsonEntryFiles.ts`. * * Here for {@link ValOps.adoptCommittedSources}: an entry's committed content * is resolved through the marker's own `import()`, which caches, so a save is * the only thing that can tell the server what the entry now holds. * * Only modules whose patches applied cleanly appear; a module that errored * contributes nothing, and `/save` refuses the commit anyway. */ patchedJsonEntries: Record>; /** * Previous source files that were patched */ previousSourceFiles: Record; /** * Each changed module's Source after this commit, and its schema. * * This is what makes a commit restorable. See the comment where it is built. */ moduleVersions: Record; /** * Diagnosis only: what the source file looks like with the appliable patches * applied, for modules that had at least one unappliable patch. Populated * only when `prepare` is called with `continueOnError`. Never committed. */ partiallyPatchedSourceFiles: Record; /** * The file path and patch id in which they appear of binary files that are ready to be committed / saved */ patchedBinaryFilesDescriptors: Record; /** * Source file patches that were successfully applied to get to this result */ appliedPatches: Record; hasErrors: boolean; sourceFilePatchErrors: Record; binaryFilePatchErrors: Record; /** * The patches that could not be applied, keyed by patch id. * * Same information as `sourceFilePatchErrors`, but attributed to the patch * that caused it, which is what a caller needs in order to report or remove * it. Without `continueOnError` this holds the first failing patch of each * module (the rest of that module's chain is never tried); with it, all of * them. */ unappliablePatches: Record; skippedPatches: Record; triedPatches: Record; }; export type PatchErrors = Record; export type PatchReadError = { patchId: PatchId; message: string; } | { parentPatchId: ParentPatchId; message: string; }; /** * The patches a json entry render should apply, out of the whole chain. * * Three rules, and the second is the one that was missing. A draft page renders * `jsonValues` entries beside module content, and only the modules were scoped * — so one screen showed the caller's own view for its modules and base plus * EVERY pending patch on the branch for the entries beside them, including * another author's half-finished edit rendered as though it were live. * * 1. this module's, since the chain is branch-wide; * 2. this caller's, when they asked to be scoped. `undefined` is "everything", * which is what every unscoped caller gets and must keep getting; * 3. not already applied — a fact about this path rather than about scoping, * and true with or without a scope. * * Filtered here rather than by asking `fetchPatches` for a list, and that is * load-bearing: both implementations read an empty `patchIds` as "no filter" * and return the whole chain. That is the right default for a caller that * cannot mean "none", and the most dangerous possible reading of a group that * is genuinely empty — it would render every unpublished patch on the branch * instead of base. It costs no round trip either: the whole chain is what the * unscoped path fetches anyway. */ export declare function scopedModulePatches(patches: T[], moduleFilePath: ModuleFilePath, patchIds: PatchId[] | undefined): T[]; export type OrderedPatches = { patches: { path: ModuleFilePath; patchId: PatchId; patch: Patch; createdAt: string; authorId: AuthorId | null; baseSha: BaseSha; appliedAt: { commitSha: CommitSha; } | null; }[]; commits?: ValCommit[]; error?: GenericErrorMessage; errors?: PatchReadError[]; unauthorized?: boolean; networkError?: boolean; }; export type OrderedPatchesMetadata = { patches: (Omit & { patch?: undefined; })[]; commits?: ValCommit[]; deployments?: ValDeployment[]; error?: GenericErrorMessage; errors?: OrderedPatches["errors"]; unauthorized?: boolean; networkError?: boolean; }; export declare function getFieldsForType(type: T): (keyof MetadataOfType & string)[]; export declare function createMetadataFromBuffer(type: BinaryFileType, mimeType: string, buffer: Buffer): OpsMetadata; export declare function guessMimeTypeFromPath(filePath: string): string | null; export declare function bufferFromDataUrl(dataUrl: string): Buffer | undefined;