import type { FigmaComponentMeta, FigmaNodeDoc, FigmaStyleMeta, VariablesResponse } from "./figma-api.js"; /** * Bumped when a reader can no longer make sense of an older cache. A cache * written by a newer format is ignored rather than misread — the import then * refetches into the current one, which costs a run's worth of API calls and * loses nothing. */ export declare const REFERENCE_CACHE_FORMAT_VERSION = 1; /** The manifest's name inside a cache directory. */ export declare const REFERENCE_CACHE_INDEX = "index.json"; /** What one cached node carries. Paths are cache-relative and POSIX-separated. */ export interface ReferenceCacheEntry { fileKey: string; /** Canonical, colon-separated (`1:42`) — the form the REST API takes. */ nodeId: string; /** * The file `version` in effect when this entry was fetched. An import * compares it against the file's current version to decide whether this node * can have moved; unequal means stale, never "wrong". */ fileVersion: string; /** ISO-8601. What "refresh oldest-first" orders on, and what dates a stale row. */ fetchedAt: string; /** Structure JSON, relative to the cache root. */ node: string; /** Rendered reference image, relative to the cache root. Absent ⇒ structure only. */ image?: string; imageFormat?: "png" | "svg"; /** * Figma `contents_only` mode used for this render. Older cache entries omit * it and therefore mean Figma's default, `true`. */ imageContentsOnly?: boolean; /** * Figma render scale used for this image. **PNG only** — the client sends * scale as `format=png&scale=…` and omits it for SVG — so an SVG entry has * none. Absent on a PNG entry means 2, which is what the client renders at * when no scale is given, not the API's bare 1. * * Recorded so a scale change is a reason to refresh. It is passed to Figma at * render time but was never persisted, so nothing could compare it — the same * shape as [imageFormat], which was persisted and compared nowhere. */ imageScale?: number; /** * What this entry records about the import's `--placeholder-fill`, in three * states: * * - **a mode** (`flat`, `checkerboard`, `#rrggbb`) — this entry carries an * empty image fill, painted that way. * - **`no-placeholder`** — a scan looked and found none, so no mode can change * a pixel of this entry. * - **absent** — written before the mode was recorded at all. Presence is * unknown, so it is due a re-read once and then settles. * * Recorded so a mode change is a reason to refresh, exactly as * [imageContentsOnly] is: the paint is applied at download, so without it the * mode reaches only nodes re-read for some other reason and a cache ends up * half normalised with nothing on it saying which half is which. * * The third state is what keeps that cheap. Recording a mode on every entry * made a switch look like a reason to re-read the whole kit — the first one on * `wear-m3-catalog` refreshed all 581 nodes to rewrite 549 of them * byte-identically, on the one lane allowed to spend the Figma token. */ imagePlaceholderFill?: string; /** * This entry is a **component set**, cached for its properties and its * variant names rather than for a picture of it (issue #296). Rendering a set * would produce a grid of every variant at once, which nothing compares * against, so `image` is absent here **by design** rather than because an * import half-finished — which is what a reader would otherwise conclude. */ structureOnly?: boolean; } /** Per-file state: the last metadata seen, and where the variables landed. */ export interface ReferenceCacheFile { version: string; lastModified?: string; fetchedAt: string; /** Variables JSON, relative to the cache root. Absent ⇒ never fetched. */ variables?: string; } /** `index.json` — the whole cache, minus the blobs. */ export interface ReferenceCacheDoc { formatVersion: number; /** Keyed by file key. */ files: Record; /** Sorted by `fileKey` then `nodeId`, so the committed diff is stable. */ entries: ReferenceCacheEntry[]; } /** The structure half of an entry: the document, and the styles it references. */ export interface CachedNodeDoc { document: FigmaNodeDoc; styles?: Record; /** * The file-level component metadata the nodes response carried. The * load-bearing field is `componentSetId`: a variant's document holds no * pointer to the set that owns its properties, so a cache without this * cannot tell that the node it holds *has* a family (issue #296). */ components?: Record; } /** `1:42` ⇒ `1-42`: a directory name that is legal on every filesystem. */ export declare function nodeDirName(nodeId: string): string; /** Cache-relative directory for one node's blobs. */ export declare function cacheEntryDir(fileKey: string, nodeId: string): string; /** The key an entry is looked up by. */ export declare function cacheKeyOf(fileKey: string, nodeId: string): string; /** An empty cache document — what a first import starts from. */ export declare function emptyReferenceCache(): ReferenceCacheDoc; /** * Read a cache directory's manifest. * * Returns `undefined` for every "there is nothing usable here" case — absent, * unreadable, not JSON, a format from the future — because all of them mean the * same thing to both callers: the import writes a fresh cache, and a run * configured to read one says so plainly instead of half-using a broken one. */ export declare function readReferenceCacheDoc(dir: string): Promise; /** * A cache directory, opened for reading. * * Blobs are read lazily and memoised: a run touches a handful of the entries a * catalog-sized cache holds, and reading 77 node documents to resolve six of * them would trade an API cost for a disk one. */ export declare class ReferenceCache { #private; readonly dir: string; readonly doc: ReferenceCacheDoc; constructor(dir: string, doc: ReferenceCacheDoc); /** Open `dir`, or `undefined` when it holds no usable cache. */ static open(dir: string): Promise; get entries(): readonly ReferenceCacheEntry[]; entry(fileKey: string, nodeId: string): ReferenceCacheEntry | undefined; file(fileKey: string): ReferenceCacheFile | undefined; /** Absolute path of a cache-relative path. */ path(relative: string): string; /** The cached structure, or `undefined` when the entry or its blob is gone. */ node(fileKey: string, nodeId: string): Promise; /** * The file's variables. `{}` when absent — the same degradation the REST * client applies for a non-Enterprise file, so a cache built without them * behaves like a file that never had them. */ variables(fileKey: string): Promise; } /** * A cache directory, opened for writing — the import's half of the contract. * * Refresh is IN PLACE by design. An entry this run could not re-read keeps the * blobs and the manifest row it already had, so "partial" costs nothing but * freshness: the branch always describes the whole catalog, and each row says * how old it is. That is the entire reason a rate-limited import converges * instead of thrashing. */ export declare class ReferenceCacheWriter { #private; readonly dir: string; constructor(dir: string, base?: ReferenceCacheDoc); /** Open `dir` for an in-place refresh of whatever it already holds. */ static open(dir: string): Promise; get doc(): ReferenceCacheDoc; entry(fileKey: string, nodeId: string): ReferenceCacheEntry | undefined; file(fileKey: string): ReferenceCacheFile | undefined; /** Record the file metadata an import observed. */ setFile(fileKey: string, meta: Omit): void; putVariables(fileKey: string, variables: VariablesResponse): Promise; /** * Write (or overwrite) one node. The image is optional so a structure fetch * that succeeded is not thrown away because the render did not — but note * that the *caller* decides whether a half-refreshed node is worth recording; * see the import, which keeps the old entry so the node retries next run. */ put(input: { fileKey: string; nodeId: string; fileVersion: string; fetchedAt: string; node: CachedNodeDoc; image?: { bytes: Uint8Array; format: "png" | "svg"; contentsOnly?: boolean; placeholderFill?: string; scale?: number; }; /** Mark a component set, which is cached without a render. */ structureOnly?: boolean; }): Promise; /** * Drop entries the caller no longer wants cached, blobs included. * * Opt-in, and the caller must be sure `keep` is the WHOLE set it cares about: * pruning against a partial list deletes references the next full run then * has to re-import. Returns the keys removed. */ prune(keep: ReadonlySet): Promise; /** Write `index.json`. Entries are sorted so the committed diff is stable. */ write(): Promise; } //# sourceMappingURL=reference-cache.d.ts.map