perf(studio-server): a host can import id stamping, file walking and history without the server (#4644)

* perf(studio-server): light entries for safe paths, hf-id stamping and project history

* docs(studio-server): list the light entries a host can import
This commit is contained in:
Miguel Ángel
2026-09-28 16:14:43 +00:00
committed by GitHub
parent cbcadbfbd0
commit e251d4c1de
8 changed files with 112 additions and 32 deletions
+3
View File
@@ -60,6 +60,9 @@ bundle: (dir, options) => bundleToSingleHtml(dir, { runtime: "inline", ...option
| --- | --- |
| `@hyperframes/studio-server/source-mutation` | Apply guarded changes to composition source |
| `@hyperframes/studio-server/screenshot-clip` | Calculate element screenshot geometry |
| `@hyperframes/studio-server/safe-path` | Resolve paths inside a project and walk its files, without the server |
| `@hyperframes/studio-server/hf-ids` | Stamp stable element ids into a project's HTML, without the server |
| `@hyperframes/studio-server/history` | Open a project's undo history, without the server |
| `@hyperframes/studio-server/manual-edits-render-script` | Reapply Studio manual edits during render |
| `@hyperframes/studio-server/studio-motion-render-script` | Reapply Studio motion edits during render |
| `@hyperframes/studio-server/draft-markers` | Work with draft gesture markers |
@@ -20,6 +20,24 @@
"types": "./dist/helpers/screenshotClip.d.ts",
"environments": ["bun", "node"]
},
"./safe-path": {
"source": "./src/helpers/safePath.ts",
"runtime": "./dist/helpers/safePath.js",
"types": "./dist/helpers/safePath.d.ts",
"environments": ["bun", "node"]
},
"./hf-ids": {
"source": "./src/helpers/hfIdPersist.ts",
"runtime": "./dist/helpers/hfIdPersist.js",
"types": "./dist/helpers/hfIdPersist.d.ts",
"environments": ["bun", "node"]
},
"./history": {
"source": "./src/history/index.ts",
"runtime": "./dist/history/index.js",
"types": "./dist/history/index.d.ts",
"environments": ["bun", "node"]
},
"./manual-edits-render-script": {
"source": "./src/helpers/manualEditsRenderScript.ts",
"runtime": "./dist/helpers/manualEditsRenderScript.js",
+30
View File
@@ -27,6 +27,24 @@
"import": "./src/helpers/screenshotClip.ts",
"types": "./src/helpers/screenshotClip.ts"
},
"./safe-path": {
"bun": "./src/helpers/safePath.ts",
"node": "./dist/helpers/safePath.js",
"import": "./src/helpers/safePath.ts",
"types": "./src/helpers/safePath.ts"
},
"./hf-ids": {
"bun": "./src/helpers/hfIdPersist.ts",
"node": "./dist/helpers/hfIdPersist.js",
"import": "./src/helpers/hfIdPersist.ts",
"types": "./src/helpers/hfIdPersist.ts"
},
"./history": {
"bun": "./src/history/index.ts",
"node": "./dist/history/index.js",
"import": "./src/history/index.ts",
"types": "./src/history/index.ts"
},
"./manual-edits-render-script": {
"bun": "./src/helpers/manualEditsRenderScript.ts",
"node": "./dist/helpers/manualEditsRenderScript.js",
@@ -88,6 +106,18 @@
"import": "./dist/helpers/screenshotClip.js",
"types": "./dist/helpers/screenshotClip.d.ts"
},
"./safe-path": {
"import": "./dist/helpers/safePath.js",
"types": "./dist/helpers/safePath.d.ts"
},
"./hf-ids": {
"import": "./dist/helpers/hfIdPersist.js",
"types": "./dist/helpers/hfIdPersist.d.ts"
},
"./history": {
"import": "./dist/history/index.js",
"types": "./dist/history/index.d.ts"
},
"./manual-edits-render-script": {
"import": "./dist/helpers/manualEditsRenderScript.js",
"types": "./dist/helpers/manualEditsRenderScript.d.ts"
@@ -1,6 +1,6 @@
import { basename, dirname, isAbsolute, join, relative, sep } from "node:path";
import { readdirSync, type Dirent } from "node:fs";
import { realpath, resolveWithinProject } from "@hyperframes/core";
import { realpath, resolveWithinProject } from "@hyperframes/core/safe-path";
// `isSafePath` lives at the package root so non-studio-api layers (compiler,
// CLI, engine) can share it without a backwards dependency on studio-api.
@@ -13,7 +13,7 @@ export {
realpath,
realProjectRoot,
resolveWithinProject,
} from "@hyperframes/core";
} from "@hyperframes/core/safe-path";
/** The real path; for a path not there (yet, or any more), the nearest existing folder's real path plus the rest. */
export function realFilePath(filePath: string): string {
@@ -0,0 +1,30 @@
export {
DEFAULT_HISTORY_ROOT,
openProjectHistory,
MAX_WINDOW_IDLE_MS,
UNDO_MODES,
type UndoMode,
type ClosedWindow,
type ProjectHistory,
type ProjectHistoryOptions,
type HistoryListItem,
type HistoryResult,
type HistoryWindow,
HistoryClosedError,
} from "./projectHistory.js";
export { HistoryBusyError } from "./ownerLock.js";
export { HistoryIdError } from "./historyId.js";
export { historyCache } from "./historyCache.js";
export {
listProjectHistories,
pruneGoneProjectHistories,
type ProjectHistoryRecord,
type PrunedHistory,
} from "./pruneHistories.js";
export {
START as HISTORY_START,
type HistoryEntry,
type HistoryEntrySide,
type HistoryFileChange,
type HistoryWho,
} from "./historyLog.js";
+1 -30
View File
@@ -2,36 +2,7 @@ export { createStudioApi } from "./createStudioApi.js";
export { createProjectSignature, affectsProjectSignature } from "./helpers/projectSignature.js";
export { compositionsAffectedBy } from "./helpers/compositionInputs.js";
export { affectsPreview } from "./helpers/previewReads.js";
export {
DEFAULT_HISTORY_ROOT,
openProjectHistory,
MAX_WINDOW_IDLE_MS,
UNDO_MODES,
type UndoMode,
type ClosedWindow,
type ProjectHistory,
type ProjectHistoryOptions,
type HistoryListItem,
type HistoryResult,
type HistoryWindow,
HistoryClosedError,
} from "./history/projectHistory.js";
export { HistoryBusyError } from "./history/ownerLock.js";
export { HistoryIdError } from "./history/historyId.js";
export { historyCache } from "./history/historyCache.js";
export {
listProjectHistories,
pruneGoneProjectHistories,
type ProjectHistoryRecord,
type PrunedHistory,
} from "./history/pruneHistories.js";
export {
START as HISTORY_START,
type HistoryEntry,
type HistoryEntrySide,
type HistoryFileChange,
type HistoryWho,
} from "./history/historyLog.js";
export * from "./history/index.js";
export type {
StudioApiAdapter,
ResolvedProject,
@@ -0,0 +1,25 @@
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { describe, expect, it, vi } from "vitest";
const packageDir = resolve(import.meta.dirname, "..");
const { subpaths } = JSON.parse(readFileSync(resolve(packageDir, "package-subpaths.json"), "utf8"));
function refuse(name: string): never {
throw new Error(`a light entry loaded ${name}`);
}
vi.mock("@hyperframes/core", () => refuse("@hyperframes/core"));
vi.mock("@hyperframes/core/compiler", () => refuse("@hyperframes/core/compiler"));
vi.mock("@hyperframes/core/lint", () => refuse("@hyperframes/core/lint"));
vi.mock("hono", () => refuse("hono"));
vi.mock("postcss", () => refuse("postcss"));
vi.mock("sharp", () => refuse("sharp"));
describe("entries a host imports for one helper", () => {
it.each(["./safe-path", "./hf-ids", "./history", "./screenshot-clip"])(
"%s loads without core's root, the API, lint or image tooling",
async (entry) => {
await expect(import(resolve(packageDir, subpaths[entry].source))).resolves.toBeDefined();
},
);
});
+3
View File
@@ -12,6 +12,9 @@ export default defineConfig({
"helpers/draftMarkers": "src/helpers/draftMarkers.ts",
"helpers/finiteMutation": "src/helpers/finiteMutation.ts",
"helpers/sourceMutation": "src/helpers/sourceMutation.ts",
"helpers/safePath": "src/helpers/safePath.ts",
"helpers/hfIdPersist": "src/helpers/hfIdPersist.ts",
"history/index": "src/history/index.ts",
},
format: ["esm"],
outDir: "dist",