import { type PatchId, type SerializedSchema } from "@valbuild/core"; import type { PatchSourceError } from "../ValOps.js"; import { result } from "@valbuild/core/fp"; import { JSONValue, Operation, Patch, PatchError } from "@valbuild/core/patch"; /** * Classification of a single patch op against a module's serialized schema, * used by the commit flow to route ops for `.jsonValues()` records: * * - `normal`: the op does not descend into a `.jsonValues()` entry; apply it to * the `.val.ts` as usual. * - `entry`: the op targets a `.jsonValues()` entry. `recordPath` is the path to * the record within the module source (empty for a root record/router), * `entryKey` is the entry key, and `subPath` is the remaining path inside the * entry (empty when the op targets the entry value itself, e.g. add/remove of * the whole entry). */ export type JsonValuesOpClass = { kind: "normal"; } | { kind: "entry"; recordPath: string[]; entryKey: string; subPath: string[]; }; /** * Walks the serialized schema following the op path. When a `.jsonValues()` * record is encountered, the next path segment is the entry key and everything * after it lives inside the entry's `*.val.json` (so it does not touch the * `.val.ts`). Returns `{ kind: "normal" }` when the op never enters a * `.jsonValues()` record. */ export declare function classifyJsonValuesOp(schema: SerializedSchema, opPath: string[]): JsonValuesOpClass; /** * Is this op a write of the WHOLE record at the root of a `.jsonValues()` * module? * * The one op {@link classifyJsonValuesOp} cannot classify: it finds the entry * key by walking the op path, and the root path has no segments to walk, so a * root write reads as `normal` and gets applied to the `.val.ts` - writing * markers over the `c.json(() => import(...))` calls that make the entries load * at all. {@link expandJsonValuesRootOp} turns it into ops that DO name a key, * which everything downstream already handles. * * `add` counts as well as `replace`: at the root both mean "the document is now * this" (see `JSONOps`), so both have to be expanded or the unexpanded one is * the same bug again. * * The value has to BE a record, and that is part of the question rather than a * check inside the expansion: a `.jsonValues()` record can be nullable, and * `null` at the root means the module no longer has a record at all - there are * no entries to write, and it is an ordinary `.val.ts` write, exactly as it was * before any of this existed. Same for any other non-record value: not a * whole-record write, so not this conversion's to route. */ export declare function isJsonValuesRootOp(schema: SerializedSchema, op: Operation): boolean; /** * One entry as it stands right now, for {@link expandJsonValuesRootOp} to * expand against. * * The key set decides add-vs-replace-vs-remove. The content, where the caller * has it, decides whether an op is emitted at all: an entry whose content is * already what the root write says is left alone rather than rewritten, so * putting a module back does not touch every `*.val.json` it did not change. * `undefined` means "this entry exists, content unknown" - which is what the * Studio's draft source has, because the source holds markers - and yields a * `replace`, the safe answer. */ export type CurrentJsonEntries = ReadonlyMap; /** * Fans a whole-record write at a `.jsonValues()` module's root out into ops * that each name an entry key. * * This is THE conversion, and it has exactly one implementation on purpose: the * commit flow (`ValOps.prepare`) and the read side that builds draft content * ({@link applyJsonValuesEntryPatches}) both expand through here, so a draft * cannot show something other than what publishing writes. A second * implementation of the same rule would differ silently, which is the whole * failure mode this exists to prevent. * * The value must be the entries' CONTENT. A module's Source is markers, not * content, so a Source handed over here is refused rather than written: that is * the shape of the original bug (a revert replaying the archived Source), and * it is not recoverable afterwards - the markers replace the entries' content * on disk. */ export declare function expandJsonValuesRootOp(op: Operation, currentEntries: CurrentJsonEntries): result.Result; /** * What a whole-record write says about ONE entry: the same rule * {@link expandJsonValuesRootOp} applies to all of them. * * For a reader that is about one entry. Expanding the whole record to find the * one op that names its key costs an op per entry per entry read - the record * squared, on exactly the large records this feature is for. The rule itself is * `writeForEntry`, shared with the expansion above, so the two cannot come to * disagree; only the loop around it differs. * * `null` means the write says nothing about this entry: it holds that content * already, or the record does not name it and it does not exist. */ export declare function expandJsonValuesRootOpForKey(op: Operation, entryKey: string, currentEntries: CurrentJsonEntries): result.Result; /** * Finds every `.jsonValues()` record in a module's schema that is NOT the * module's root, returning the path to each within the module source. * * `.jsonValues()` is only supported on a module's ROOT record/router: the * `/json` endpoint keys entries by a single string, the Studio substitutes * loaded content at the top level of the module source, and * `validateJsonValuesEntries` only visits a root record. A nested one would * silently skip content validation and hang the Studio on a 404, so we reject * it up front instead (see {@link ValOps.initSources}). */ export declare function findNestedJsonValuesRecords(schema: SerializedSchema, path?: string[]): string[][]; /** * Computes the `*.val.json` file path (relative to rootDir) and the `import(...)` * path (relative to the module's directory) for a NEW `.jsonValues()` entry, * following the locked filename convention: the file mirrors the entry key under * a folder named after the `.val.ts` (its `.val.ts` suffix becomes the folder). * * For module `/app/support/[slug]/page.val.ts` and key `/support/faq`: * - jsonPath: `/app/support/[slug]/page/support/faq.val.json` * - importPath: `./page/support/faq.val.json` */ export declare function getNewJsonEntryPaths(moduleFilePath: string, entryKey: string): result.Result<{ jsonPath: string; importPath: string; }, PatchSourceError>; /** * Rebases a patch op that targets a `.jsonValues()` entry's content so its paths * are relative to the entry's `*.val.json` root (drops the record + entry-key * prefix). Used to replay the op against the backing JSON file. */ export declare function rebaseContentOp(op: Operation, prefixLen: number): result.Result; /** The outcome of replaying pending patches onto one `.jsonValues()` entry. */ export type JsonEntryResolution = { kind: "content"; content: JSONValue | null; appliedPatchIds: PatchId[]; } | { kind: "deleted"; appliedPatchIds: PatchId[]; } | { kind: "error"; message: string; patchId?: PatchId; }; /** * Replays the ops of `patches` that target ONE `.jsonValues()` entry onto its * committed content, yielding the entry's draft content. * * This is the read-side counterpart to the commit flow in `ValOps.prepare`: * both route ops with {@link classifyJsonValuesOp} and replay content sub-ops * with {@link rebaseContentOp}, but this one produces a value instead of files * and never touches the `.val.ts`. * * Root-only, like the rest of the `.jsonValues()` machinery: ops targeting a * nested record are ignored (nested `.jsonValues()` is rejected at startup). */ export declare function applyJsonValuesEntryPatches(args: { serializedSchema: SerializedSchema | undefined; entryKey: string; /** `undefined` when the entry does not exist in the committed source. */ baseContent: JSONValue | undefined; /** Ordered, already filtered to the entry's module. */ patches: { patchId: PatchId; patch: Patch; }[]; }): JsonEntryResolution; /** * Resolves an EXISTING entry's `*.val.json` path (relative to rootDir) from the * `import(...)` path recorded in the `.val.ts` thunk (from * {@link analyzeJsonValuesEntries}). Existing files may have been hand-placed, * so the import path is authoritative (hybrid authoring). */ export declare function resolveExistingJsonPath(moduleFilePath: string, importPath: string): string;