@hyperframes/studio-server 0.8.191 → 0.8.192

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.
@@ -7,7 +7,7 @@ import {
7
7
 
8
8
  // src/history/projectHistory.ts
9
9
  import { randomUUID as randomUUID7 } from "crypto";
10
- import { mkdir as mkdir2, readdir as readdir3, rm as rm3, rmdir, writeFile } from "fs/promises";
10
+ import { mkdir as mkdir2, readdir as readdir3, rm as rm3, rmdir, stat as stat2, writeFile } from "fs/promises";
11
11
  import {
12
12
  existsSync as existsSync3,
13
13
  lstatSync as lstatSync4,
@@ -37,9 +37,9 @@ function hashVersion(hex) {
37
37
  function hashOfVersion(version) {
38
38
  return /^"sha256:([0-9a-f]{64})"$/.exec(version)?.[1];
39
39
  }
40
- function settledFileTag(stat2, now = Date.now()) {
41
- if (now - stat2.ctimeMs < 3e3) return null;
42
- return [stat2.ino, stat2.ctimeMs, stat2.size].map((n) => n.toString(36)).join("-");
40
+ function settledFileTag(stat4, now = Date.now()) {
41
+ if (now - stat4.ctimeMs < 3e3) return null;
42
+ return [stat4.ino, stat4.ctimeMs, stat4.size].map((n) => n.toString(36)).join("-");
43
43
  }
44
44
  function createWriteToken(requestToken) {
45
45
  const token = requestToken?.trim();
@@ -345,8 +345,8 @@ function signatureEntryStat(projectDir, entry, file, outputs) {
345
345
  if (isSkippedEntry(entry) || !isPathWithin(projectDir, file)) return null;
346
346
  if (isRenderOutput(projectDir, file, outputs)) return null;
347
347
  try {
348
- const stat2 = lstatSync(file);
349
- return stat2.isSymbolicLink() ? null : stat2;
348
+ const stat4 = lstatSync(file);
349
+ return stat4.isSymbolicLink() ? null : stat4;
350
350
  } catch {
351
351
  return null;
352
352
  }
@@ -360,17 +360,17 @@ function collectProjectSignatureFiles(projectDir, dir, files, outputs = readRend
360
360
  }
361
361
  for (const entry of entries) {
362
362
  const file = resolve2(dir, entry);
363
- const stat2 = signatureEntryStat(projectDir, entry, file, outputs);
364
- if (!stat2) continue;
365
- if (stat2.isDirectory()) {
363
+ const stat4 = signatureEntryStat(projectDir, entry, file, outputs);
364
+ if (!stat4) continue;
365
+ if (stat4.isDirectory()) {
366
366
  collectProjectSignatureFiles(projectDir, file, files, outputs);
367
- } else if (stat2.isFile()) {
367
+ } else if (stat4.isFile()) {
368
368
  files.push({
369
369
  file,
370
- mtimeMs: stat2.mtimeMs,
371
- ctimeMs: stat2.ctimeMs,
372
- size: stat2.size,
373
- textContentEligible: isTextContentEligible(file, stat2.size)
370
+ mtimeMs: stat4.mtimeMs,
371
+ ctimeMs: stat4.ctimeMs,
372
+ size: stat4.size,
373
+ textContentEligible: isTextContentEligible(file, stat4.size)
374
374
  });
375
375
  }
376
376
  }
@@ -380,19 +380,19 @@ function collectProjectSignatureManifestFiles(projectDir, files) {
380
380
  for (const manifestPath of STUDIO_SIGNATURE_MANIFEST_PATHS) {
381
381
  const file = resolve2(projectDir, manifestPath);
382
382
  if (seen.has(file) || !isPathWithin(projectDir, file)) continue;
383
- let stat2;
383
+ let stat4;
384
384
  try {
385
- stat2 = lstatSync(file);
385
+ stat4 = lstatSync(file);
386
386
  } catch {
387
387
  continue;
388
388
  }
389
- if (stat2.isSymbolicLink() || !stat2.isFile()) continue;
389
+ if (stat4.isSymbolicLink() || !stat4.isFile()) continue;
390
390
  files.push({
391
391
  file,
392
- mtimeMs: stat2.mtimeMs,
393
- ctimeMs: stat2.ctimeMs,
394
- size: stat2.size,
395
- textContentEligible: isTextContentEligible(file, stat2.size)
392
+ mtimeMs: stat4.mtimeMs,
393
+ ctimeMs: stat4.ctimeMs,
394
+ size: stat4.size,
395
+ textContentEligible: isTextContentEligible(file, stat4.size)
396
396
  });
397
397
  seen.add(file);
398
398
  }
@@ -540,11 +540,21 @@ async function openBlobStore(dir) {
540
540
  total += size;
541
541
  return hash;
542
542
  },
543
+ reference: (absPath) => hashFile(absPath),
543
544
  has: (hash) => sizes.has(hash),
544
545
  read: async (hash) => readBlob(dir, hash),
545
546
  writeTo: async (hash, absPath, beforeReplace) => cloneOrCopy(pathOf(hash), absPath, beforeReplace),
546
547
  bytes: () => total,
547
548
  size: (hash) => sizes.get(hash) ?? 0,
549
+ async drop(hashes) {
550
+ for (const hash of hashes) {
551
+ const size = sizes.get(hash);
552
+ if (size === void 0) continue;
553
+ await rm(pathOf(hash), { force: true });
554
+ sizes.delete(hash);
555
+ total -= size;
556
+ }
557
+ },
548
558
  async prune(keep) {
549
559
  for (const [hash, size] of sizes) {
550
560
  if (keep.has(hash)) continue;
@@ -556,6 +566,42 @@ async function openBlobStore(dir) {
556
566
  };
557
567
  }
558
568
 
569
+ // src/history/uncopied.ts
570
+ import { extname as extname2 } from "path";
571
+ var MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
572
+ ".mp4",
573
+ ".mov",
574
+ ".m4v",
575
+ ".webm",
576
+ ".mkv",
577
+ ".avi",
578
+ ".mxf",
579
+ ".m2ts",
580
+ ".mp3",
581
+ ".wav",
582
+ ".aac",
583
+ ".m4a",
584
+ ".flac",
585
+ ".ogg",
586
+ ".opus"
587
+ ]);
588
+ var COPY_NOTHING_ABOVE_BYTES = 64 * 1024 ** 2;
589
+ var isUncopied = (path, size) => size > COPY_NOTHING_ABOVE_BYTES || MEDIA_EXTENSIONS.has(extname2(path).toLowerCase());
590
+ function uncopiedOnlyHashes(log, sizeOf) {
591
+ const copied = /* @__PURE__ */ new Set();
592
+ const uncopied = /* @__PURE__ */ new Set();
593
+ const note = (path, hash) => {
594
+ if (hash) (isUncopied(path, sizeOf(hash)) ? uncopied : copied).add(hash);
595
+ };
596
+ for (const [path, hash] of log.baseline) note(path, hash);
597
+ for (const entry of log.entries)
598
+ for (const file of entry.files) {
599
+ note(file.path, file.before);
600
+ note(file.path, file.after);
601
+ }
602
+ return [...uncopied].filter((hash) => !copied.has(hash));
603
+ }
604
+
559
605
  // src/history/pruneHistories.ts
560
606
  import { randomUUID as randomUUID5 } from "crypto";
561
607
  import { readdirSync as readdirSync2, readFileSync as readFileSync5, renameSync as renameSync3, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
@@ -740,13 +786,13 @@ function projectGone(dir, id, dev, tempDir) {
740
786
  }
741
787
  function diskStillHere(dir, dev, tempDir) {
742
788
  let at = dir;
743
- let stat2 = statSync3(at, { throwIfNoEntry: false });
744
- while (!stat2) {
789
+ let stat4 = statSync3(at, { throwIfNoEntry: false });
790
+ while (!stat4) {
745
791
  if (dirname2(at) === at) return false;
746
792
  at = dirname2(at);
747
- stat2 = statSync3(at, { throwIfNoEntry: false });
793
+ stat4 = statSync3(at, { throwIfNoEntry: false });
748
794
  }
749
- if (typeof dev === "number") return stat2.dev === dev;
795
+ if (typeof dev === "number") return stat4.dev === dev;
750
796
  return at === dir || isSafePath(tempDir, at);
751
797
  }
752
798
  function lastUsed(home) {
@@ -866,12 +912,12 @@ function stillPendingAfterUnlink(item) {
866
912
  let descriptor;
867
913
  try {
868
914
  descriptor = openSync(item.path, constants2.O_RDONLY | constants2.O_NOFOLLOW);
869
- const stat2 = fstatSync(descriptor);
870
- if (!stat2.isFile() || stat2.isSymbolicLink()) return false;
915
+ const stat4 = fstatSync(descriptor);
916
+ if (!stat4.isFile() || stat4.isSymbolicLink()) return false;
871
917
  const hash = createHash4("sha256").update(readFileSync6(descriptor)).digest("hex");
872
918
  if (hash !== item.hash) return false;
873
919
  const current = lstatSync2(item.path);
874
- if (current.dev !== stat2.dev || current.ino !== stat2.ino) return false;
920
+ if (current.dev !== stat4.dev || current.ino !== stat4.ino) return false;
875
921
  unlinkSync(item.path);
876
922
  return false;
877
923
  } catch (error) {
@@ -988,8 +1034,8 @@ function ownerPid(owner) {
988
1034
  const pid = owner.pid;
989
1035
  return typeof pid === "number" && Number.isSafeInteger(pid) && pid > 0 ? pid : null;
990
1036
  }
991
- function ordinaryOwner(stat2) {
992
- return stat2.isFile() && stat2.nlink === 1;
1037
+ function ordinaryOwner(stat4) {
1038
+ return stat4.isFile() && stat4.nlink === 1;
993
1039
  }
994
1040
  function sameOwnerFile(before, current) {
995
1041
  return current.dev === before.dev && current.ino === before.ino;
@@ -1063,8 +1109,8 @@ function runsOnDisk(root) {
1063
1109
  return entries.filter((entry) => entry.isDirectory() && UUID.test(entry.name)).flatMap((entry) => {
1064
1110
  const path = join6(root, entry.name);
1065
1111
  try {
1066
- const stat2 = lstatSync3(path);
1067
- return [{ path, stat: stat2 }];
1112
+ const stat4 = lstatSync3(path);
1113
+ return [{ path, stat: stat4 }];
1068
1114
  } catch {
1069
1115
  return [];
1070
1116
  }
@@ -1078,10 +1124,10 @@ function planSnapshotCleanup(project, state, now = Date.now()) {
1078
1124
  const runs = runsOnDisk(root);
1079
1125
  for (const run of runs.slice(0, SNAPSHOT_RUNS_TO_KEEP)) keep.add(run.path);
1080
1126
  const pending = [];
1081
- for (const { path, stat: stat2 } of runs) {
1082
- if (keep.has(path) || now - stat2.mtimeMs < SNAPSHOT_GRACE_MS || liveOwner(path)) continue;
1127
+ for (const { path, stat: stat4 } of runs) {
1128
+ if (keep.has(path) || now - stat4.mtimeMs < SNAPSHOT_GRACE_MS || liveOwner(path)) continue;
1083
1129
  try {
1084
- const run = { path, dev: stat2.dev, ino: stat2.ino, mtimeMs: stat2.mtimeMs };
1130
+ const run = { path, dev: stat4.dev, ino: stat4.ino, mtimeMs: stat4.mtimeMs };
1085
1131
  if (!unchangedRun(project, run)) continue;
1086
1132
  pending.push(run);
1087
1133
  retireReferences(records2, root, path);
@@ -1726,6 +1772,7 @@ var Engine = class {
1726
1772
  );
1727
1773
  if (!log) return this.firstOpen();
1728
1774
  this.log = withoutHiddenPaths(log);
1775
+ await this.reclaimUncopied();
1729
1776
  if (!this.log.keepsLedger) await this.takeInUnnamedLedger();
1730
1777
  const cache = this.readStatCache();
1731
1778
  const last = this.log.entries.at(-1)?.id ?? START;
@@ -1739,6 +1786,10 @@ var Engine = class {
1739
1786
  this.reopenClosedWindow();
1740
1787
  await this.settleAll();
1741
1788
  }
1789
+ /** Copies an older version took of files that are no longer copied; text and small files are never touched. */
1790
+ async reclaimUncopied() {
1791
+ await this.blobs.drop(uncopiedOnlyHashes(this.log, (hash) => this.blobs.size(hash)));
1792
+ }
1742
1793
  hearWrites() {
1743
1794
  const realDir = realFilePath(this.dir);
1744
1795
  this.stopHearing = onFileOverwritten((absPath, version, bytes) => {
@@ -1753,7 +1804,8 @@ var Engine = class {
1753
1804
  chainBackFrom(this.overwritten.get(path), replaced).set(after, replaced)
1754
1805
  );
1755
1806
  this.queue(async () => {
1756
- if (!this.blobs.has(replaced)) await this.storeBytes(bytes);
1807
+ if (!this.blobs.has(replaced) && !isUncopied(path, bytes.length))
1808
+ await this.storeBytes(bytes);
1757
1809
  }).catch((error) => this.options.onError?.(error));
1758
1810
  });
1759
1811
  }
@@ -1832,7 +1884,8 @@ var Engine = class {
1832
1884
  }
1833
1885
  async recordAdopted(file, sweptAt, stored) {
1834
1886
  if (stored === null || this.whereFolder() !== "here") return;
1835
- const hash = this.blobs.has(stored) ? stored : await this.storeIfPresent(file.path);
1887
+ const kept = this.blobs.has(stored) || isUncopied(file.path, file.size);
1888
+ const hash = kept ? stored : await this.storeIfPresent(file.path);
1836
1889
  if (hash === null) return;
1837
1890
  const change = { path: file.path, before: null, after: hash };
1838
1891
  const found = this.overwrittenBy(change, void 0, /* @__PURE__ */ new Set());
@@ -1908,22 +1961,24 @@ var Engine = class {
1908
1961
  /** The file's hash once copied in; null when it was removed before the copy (the next sweep records that). */
1909
1962
  async storeIfPresent(path) {
1910
1963
  try {
1911
- return await this.blobs.put(join8(this.dir, path));
1964
+ const abs = join8(this.dir, path);
1965
+ const { size } = await stat2(abs);
1966
+ return await (isUncopied(path, size) ? this.blobs.reference(abs) : this.blobs.put(abs));
1912
1967
  } catch (error) {
1913
1968
  if (error.code === "ENOENT") return null;
1914
1969
  throw error;
1915
1970
  }
1916
1971
  }
1917
- async observe(path, stat2, at) {
1972
+ async observe(path, stat4, at) {
1918
1973
  const known = this.tracked.get(path) ?? { hash: null, stat: null };
1919
- if (stat2 && known.stat === stat2) return false;
1974
+ if (stat4 && known.stat === stat4) return false;
1920
1975
  const heard = this.overwritten.get(path);
1921
1976
  const hash = await this.storeIfPresent(path);
1922
1977
  if (hash === null || this.whereFolder() !== "here") return false;
1923
- this.tracked.set(path, { hash, stat: stat2 });
1978
+ this.tracked.set(path, { hash, stat: stat4 });
1924
1979
  this.forgetWritesBefore(path, hash, heard);
1925
1980
  if (known.hash !== hash) await this.record(path, known.hash, hash, at);
1926
- return known.hash !== hash || known.stat !== stat2;
1981
+ return known.hash !== hash || known.stat !== stat4;
1927
1982
  }
1928
1983
  async record(path, before, after, at) {
1929
1984
  const endedBeforeThisWrite = this.windows.filter((open) => !takesWrite(open, at));
@@ -2221,9 +2276,9 @@ var Engine = class {
2221
2276
  const parts = path.split("/");
2222
2277
  for (let depth = 1; depth <= parts.length; depth += 1) {
2223
2278
  const at = parts.slice(0, depth).join("/");
2224
- const stat2 = lstatSync4(join8(this.dir, at), { throwIfNoEntry: false });
2225
- if (!stat2) return void 0;
2226
- if (at === path || !stat2.isDirectory()) return { at, stat: stat2 };
2279
+ const stat4 = lstatSync4(join8(this.dir, at), { throwIfNoEntry: false });
2280
+ if (!stat4) return void 0;
2281
+ if (at === path || !stat4.isDirectory()) return { at, stat: stat4 };
2227
2282
  }
2228
2283
  return void 0;
2229
2284
  }
@@ -2242,6 +2297,11 @@ var Engine = class {
2242
2297
  async writeAs(who, label, target, extra) {
2243
2298
  this.assertWritable();
2244
2299
  const changes = [...target].filter(([path, hash]) => (this.tracked.get(path)?.hash ?? null) !== hash).sort(([, a], [, b]) => Number(a !== null) - Number(b !== null));
2300
+ const gone = changes.find(([, hash]) => hash !== null && !this.blobs.has(hash));
2301
+ if (gone)
2302
+ throw new Error(
2303
+ `An earlier version of ${gone[0]} is not kept, so this cannot be brought back.`
2304
+ );
2245
2305
  const deleted = new Set(changes.filter(([, hash]) => hash === null).map(([path]) => path));
2246
2306
  const blocked = changes.filter(([, hash]) => hash !== null).map(([path]) => this.inTheWay(path, deleted)).find(Boolean);
2247
2307
  if (blocked) throw new Error(`${blocked} is in the way; move or delete it, then try again.`);
@@ -2408,6 +2468,9 @@ var Engine = class {
2408
2468
  if ((await readdir3(dest).catch(missingIsEmpty)).length > 0)
2409
2469
  throw new Error(`Checkout writes into an empty folder only: ${dest}`);
2410
2470
  const files = manifestAround(this.log, entryId, side);
2471
+ for (const [path, hash] of files)
2472
+ if (!this.blobs.has(hash))
2473
+ throw new Error(`The version of ${path} from then is not kept.`);
2411
2474
  try {
2412
2475
  for (const [path, hash] of files) await this.blobs.writeTo(hash, join8(dest, path));
2413
2476
  } catch (error) {
@@ -2513,6 +2576,66 @@ function readProjectHistory(options) {
2513
2576
  };
2514
2577
  }
2515
2578
 
2579
+ // src/history/reclaimUncopied.ts
2580
+ import { readdirSync as readdirSync5 } from "fs";
2581
+ import { rm as rm4, stat as stat3 } from "fs/promises";
2582
+ import { join as join9 } from "path";
2583
+ function historyIds(historyRoot) {
2584
+ try {
2585
+ return readdirSync5(historyRoot).filter(isHistoryId);
2586
+ } catch {
2587
+ return [];
2588
+ }
2589
+ }
2590
+ function namedHashes(log) {
2591
+ const hashes = new Set(log.baseline.values());
2592
+ for (const entry of log.entries) {
2593
+ for (const file of entry.files) {
2594
+ if (file.before) hashes.add(file.before);
2595
+ if (file.after) hashes.add(file.after);
2596
+ }
2597
+ }
2598
+ return hashes;
2599
+ }
2600
+ async function blobSizes(blobs, hashes) {
2601
+ const sizes = /* @__PURE__ */ new Map();
2602
+ for (const hash of hashes)
2603
+ sizes.set(hash, (await stat3(blobPath(blobs, hash)).catch(() => null))?.size ?? 0);
2604
+ return sizes;
2605
+ }
2606
+ async function reclaimOne(home, dryRun) {
2607
+ const log = readLog(join9(home, "log.jsonl"), () => {
2608
+ });
2609
+ if (!log) return 0;
2610
+ const blobs = join9(home, "blobs");
2611
+ const sizes = await blobSizes(blobs, namedHashes(log));
2612
+ let bytes = 0;
2613
+ for (const hash of uncopiedOnlyHashes(log, (known) => sizes.get(known) ?? 0)) {
2614
+ const size = sizes.get(hash) ?? 0;
2615
+ if (size === 0) continue;
2616
+ bytes += size;
2617
+ if (!dryRun) await rm4(blobPath(blobs, hash), { force: true });
2618
+ }
2619
+ return bytes;
2620
+ }
2621
+ async function reclaimUncopiedBlobs(historyRoot, { dryRun = false, onError } = {}) {
2622
+ const reclaimed = [];
2623
+ for (const id of historyIds(historyRoot)) {
2624
+ const home = join9(historyRoot, id);
2625
+ let release;
2626
+ try {
2627
+ release = await takeHistoryOwnership(home, 0);
2628
+ const bytes = await reclaimOne(home, dryRun);
2629
+ if (bytes > 0) reclaimed.push({ id, bytes });
2630
+ } catch (error) {
2631
+ if (!(error instanceof HistoryBusyError)) onError?.(error);
2632
+ } finally {
2633
+ release?.();
2634
+ }
2635
+ }
2636
+ return reclaimed;
2637
+ }
2638
+
2516
2639
  // src/history/historyCache.ts
2517
2640
  function historyCache(open) {
2518
2641
  const histories = /* @__PURE__ */ new Map();
@@ -2571,5 +2694,6 @@ export {
2571
2694
  HistoryClosedError,
2572
2695
  openProjectHistory,
2573
2696
  readProjectHistory,
2697
+ reclaimUncopiedBlobs,
2574
2698
  historyCache
2575
2699
  };
@@ -8,6 +8,7 @@ import { isAbsolute, relative, resolve } from "path";
8
8
  import { promisify } from "util";
9
9
  import { isAudibleVideoElement } from "@hyperframes/core/audible-video";
10
10
  import { formatAudioGain, MAX_AUDIO_GAIN_DB } from "@hyperframes/core/audio-gain";
11
+ import { decodeAuthoredAttribute } from "@hyperframes/parsers";
11
12
  import { findFfBinary } from "@hyperframes/parsers/ff-binaries";
12
13
  import { readMediaOffsetSeconds } from "@hyperframes/parsers/media-duration";
13
14
 
@@ -46,7 +47,14 @@ function attributeFromMatch(match, absoluteOffset) {
46
47
  const whole = match[0];
47
48
  const localValueStart = attributeValueStart(whole);
48
49
  const valueStart = absoluteOffset + match.index + localValueStart;
49
- return [name, { value, valueStart, valueEnd: valueStart + value.length }];
50
+ return [
51
+ name,
52
+ {
53
+ value: decodeAuthoredAttribute(value),
54
+ valueStart,
55
+ valueEnd: valueStart + value.length
56
+ }
57
+ ];
50
58
  }
51
59
  function matchedAttributeValue(match) {
52
60
  return match[2] ?? match[3] ?? match[4];
@@ -11,7 +11,7 @@ import {
11
11
  requiredFfmpeg,
12
12
  resolveLocalAudioPath,
13
13
  updateAudioVolume
14
- } from "../chunk-ERQWOZ7E.js";
14
+ } from "../chunk-QZWVJKLI.js";
15
15
  import "../chunk-QF7MFJJU.js";
16
16
  import "../chunk-SVQFE44C.js";
17
17
  import "../chunk-L3HJVTQE.js";
@@ -1,6 +1,16 @@
1
1
  import { P as ProjectHistory, f as HistoryTarget, g as HistoryWho } from '../projectHistory-ByC9sa3X.js';
2
2
  export { C as ClosedWindow, D as DEFAULT_HISTORY_ROOT, S as HISTORY_START, H as HistoryClosedError, a as HistoryEntry, b as HistoryEntrySide, c as HistoryFileChange, d as HistoryListItem, e as HistoryResult, h as HistoryWindow, M as MAX_WINDOW_IDLE_MS, i as ProjectHistoryOptions, j as ProjectHistoryView, U as UNDO_MODES, k as UndoMode, o as openProjectHistory, r as readProjectHistory } from '../projectHistory-ByC9sa3X.js';
3
3
 
4
+ interface ReclaimedHistory {
5
+ id: string;
6
+ bytes: number;
7
+ }
8
+ /** Drops the media copies older versions left in every history no running process holds; the same rule as on open. */
9
+ declare function reclaimUncopiedBlobs(historyRoot: string, { dryRun, onError }?: {
10
+ dryRun?: boolean;
11
+ onError?: (error: unknown) => void;
12
+ }): Promise<ReclaimedHistory[]>;
13
+
4
14
  declare class HistoryBusyError extends Error {
5
15
  readonly pid: number;
6
16
  readonly url?: string | undefined;
@@ -138,4 +148,4 @@ declare function registerRenderOutput(projectDir: string, output: string): Promi
138
148
  /** Explicit outputs remain user evidence even when reusing a managed UUID folder. */
139
149
  declare function retainSnapshotOutput(project: string, path: string): void;
140
150
 
141
- export { type ArtifactCapture, type ArtifactInput, type ArtifactRecord, HistoryBusyError, HistoryIdError, HistoryTarget, HistoryWho, ProjectHistory, type ProjectHistoryRecord, type PrunedHistory, type RenderRecord, type VideoState, artifactDirectory, beginArtifactCapture, finishSnapshotRun, forgetRecordedArtifacts, historyCache, listProjectHistories, pruneGoneProjectHistories, recordArtifact, recordArtifacts, recordRenderSuccess, registerRenderOutput, retainSnapshotOutput };
151
+ export { type ArtifactCapture, type ArtifactInput, type ArtifactRecord, HistoryBusyError, HistoryIdError, HistoryTarget, HistoryWho, ProjectHistory, type ProjectHistoryRecord, type PrunedHistory, type ReclaimedHistory, type RenderRecord, type VideoState, artifactDirectory, beginArtifactCapture, finishSnapshotRun, forgetRecordedArtifacts, historyCache, listProjectHistories, pruneGoneProjectHistories, reclaimUncopiedBlobs, recordArtifact, recordArtifacts, recordRenderSuccess, registerRenderOutput, retainSnapshotOutput };
@@ -15,12 +15,13 @@ import {
15
15
  openProjectHistory,
16
16
  pruneGoneProjectHistories,
17
17
  readProjectHistory,
18
+ reclaimUncopiedBlobs,
18
19
  recordArtifact,
19
20
  recordArtifacts,
20
21
  recordRenderSuccess,
21
22
  registerRenderOutput,
22
23
  retainSnapshotOutput
23
- } from "../chunk-YIMSBQIO.js";
24
+ } from "../chunk-B4NDV4NM.js";
24
25
  import "../chunk-QF7MFJJU.js";
25
26
  import "../chunk-SVQFE44C.js";
26
27
  import "../chunk-L3HJVTQE.js";
@@ -41,6 +42,7 @@ export {
41
42
  openProjectHistory,
42
43
  pruneGoneProjectHistories,
43
44
  readProjectHistory,
45
+ reclaimUncopiedBlobs,
44
46
  recordArtifact,
45
47
  recordArtifacts,
46
48
  recordRenderSuccess,
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import { Hono } from 'hono';
2
2
  import { S as StudioApiAdapter, P as PreviewDocumentStore, M as MediaProcessingJobState } from './mediaProxyPreview-CAiGJucG.js';
3
3
  export { L as LintResult, a as PreviewApiAdapter, R as RenderJobState, b as ResolvedProject, c as StudioSelectionResponse, d as StudioSelectionSnapshot, e as StudioSelectionTextField } from './mediaProxyPreview-CAiGJucG.js';
4
4
  export { C as ClosedWindow, D as DEFAULT_HISTORY_ROOT, S as HISTORY_START, H as HistoryClosedError, a as HistoryEntry, b as HistoryEntrySide, c as HistoryFileChange, d as HistoryListItem, e as HistoryResult, f as HistoryTarget, g as HistoryWho, h as HistoryWindow, M as MAX_WINDOW_IDLE_MS, P as ProjectHistory, i as ProjectHistoryOptions, j as ProjectHistoryView, U as UNDO_MODES, k as UndoMode, o as openProjectHistory, r as readProjectHistory } from './projectHistory-ByC9sa3X.js';
5
- export { ArtifactCapture, ArtifactInput, ArtifactRecord, HistoryBusyError, HistoryIdError, ProjectHistoryRecord, PrunedHistory, RenderRecord, VideoState, artifactDirectory, beginArtifactCapture, finishSnapshotRun, forgetRecordedArtifacts, historyCache, listProjectHistories, pruneGoneProjectHistories, recordArtifact, recordArtifacts, recordRenderSuccess, registerRenderOutput, retainSnapshotOutput } from './history/index.js';
5
+ export { ArtifactCapture, ArtifactInput, ArtifactRecord, HistoryBusyError, HistoryIdError, ProjectHistoryRecord, PrunedHistory, ReclaimedHistory, RenderRecord, VideoState, artifactDirectory, beginArtifactCapture, finishSnapshotRun, forgetRecordedArtifacts, historyCache, listProjectHistories, pruneGoneProjectHistories, reclaimUncopiedBlobs, recordArtifact, recordArtifacts, recordRenderSuccess, registerRenderOutput, retainSnapshotOutput } from './history/index.js';
6
6
  export { CACHE_DIR_NAME as PROXY_CACHE_DIR_NAME } from './helpers/proxyTranscoder.js';
7
7
  export { walkDir } from './helpers/safePath.js';
8
8
  export { stampProjectHfIds } from './helpers/hfIdPersist.js';
package/dist/index.js CHANGED
@@ -51,6 +51,7 @@ import {
51
51
  openProjectHistory,
52
52
  pruneGoneProjectHistories,
53
53
  readProjectHistory,
54
+ reclaimUncopiedBlobs,
54
55
  recordArtifact,
55
56
  recordArtifacts,
56
57
  recordFileWriteReceipt,
@@ -60,7 +61,7 @@ import {
60
61
  resolveProjectSignature,
61
62
  retainSnapshotOutput,
62
63
  settledFileTag
63
- } from "./chunk-YIMSBQIO.js";
64
+ } from "./chunk-B4NDV4NM.js";
64
65
  import {
65
66
  clearElementScreenshotIsolation,
66
67
  getElementScreenshotClip
@@ -74,7 +75,7 @@ import {
74
75
  requestedProjectPath,
75
76
  requiredFfmpeg,
76
77
  resolveLocalAudioPath
77
- } from "./chunk-ERQWOZ7E.js";
78
+ } from "./chunk-QZWVJKLI.js";
78
79
  import {
79
80
  injectMediaCodecMap,
80
81
  isAutoProxyEnabled,
@@ -6807,6 +6808,7 @@ export {
6807
6808
  patchElementInHtml,
6808
6809
  pruneGoneProjectHistories,
6809
6810
  readProjectHistory,
6811
+ reclaimUncopiedBlobs,
6810
6812
  recordArtifact,
6811
6813
  recordArtifacts,
6812
6814
  recordRenderSuccess,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/studio-server",
3
- "version": "0.8.191",
3
+ "version": "0.8.192",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/heygen-com/hyperframes",
@@ -84,8 +84,8 @@
84
84
  "linkedom": "^0.18.12",
85
85
  "postcss": "^8.5.8",
86
86
  "postcss-selector-parser": "^7.1.2",
87
- "@hyperframes/parsers": "0.8.191",
88
- "@hyperframes/core": "0.8.191"
87
+ "@hyperframes/core": "0.8.192",
88
+ "@hyperframes/parsers": "0.8.192"
89
89
  },
90
90
  "devDependencies": {
91
91
  "@types/node": "^25.0.10",