@hyperframes/studio-server 0.9.25 → 0.9.27

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 readdir4, rm as rm3, rmdir, stat as stat2, writeFile } from "fs/promises";
10
+ import { mkdir as mkdir2, readdir as readdir4, rm as rm3, rmdir, stat as stat2, writeFile as writeFile2 } from "fs/promises";
11
11
  import {
12
12
  existsSync as existsSync3,
13
13
  lstatSync as lstatSync4,
@@ -603,7 +603,17 @@ async function createProjectSignatureAsync(projectDir, excluding = /* @__PURE__
603
603
  // src/history/blobStore.ts
604
604
  import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
605
605
  import { constants, createReadStream, createWriteStream } from "fs";
606
- import { copyFile, mkdir, open, readdir as readdir2, readFile as readFile2, rm, stat } from "fs/promises";
606
+ import {
607
+ chmod,
608
+ copyFile,
609
+ mkdir,
610
+ open,
611
+ readdir as readdir2,
612
+ readFile as readFile2,
613
+ rm,
614
+ stat,
615
+ writeFile
616
+ } from "fs/promises";
607
617
  import { dirname, join as join2 } from "path";
608
618
  import { pipeline } from "stream/promises";
609
619
  import { atomicTempPath } from "@hyperframes/core/atomic-file";
@@ -642,6 +652,7 @@ async function stopServingAndClose(path) {
642
652
 
643
653
  // src/history/blobStore.ts
644
654
  var BLOB_HASH = /^[0-9a-f]{64}$/;
655
+ var READ_ONCE_AT_MOST_BYTES = 64 * 1024;
645
656
  function blobPath(dir, hash) {
646
657
  if (!BLOB_HASH.test(hash)) throw new Error("That is not a history blob.");
647
658
  return join2(dir, hash.slice(0, 2), hash);
@@ -652,6 +663,15 @@ async function hashFile(path) {
652
663
  for await (const chunk of createReadStream(path)) hash.update(chunk);
653
664
  return hash.digest("hex");
654
665
  }
666
+ async function readIfSmall(path) {
667
+ const handle = await open(path, "r");
668
+ try {
669
+ const { size, mode } = await handle.stat();
670
+ return size > READ_ONCE_AT_MOST_BYTES ? null : { bytes: await handle.readFile(), mode };
671
+ } finally {
672
+ await handle.close();
673
+ }
674
+ }
655
675
  async function cloneOrStream(from, to) {
656
676
  try {
657
677
  await copyFile(from, to, constants.COPYFILE_FICLONE_FORCE);
@@ -734,8 +754,35 @@ async function openBlobStore(dir) {
734
754
  asideSizes.delete(hash);
735
755
  holds.delete(hash);
736
756
  };
757
+ const file = (hash, size) => {
758
+ if (!sizes.has(hash)) total += size;
759
+ sizes.set(hash, size);
760
+ };
761
+ const withFolderMadeIfMissing = (folder, write) => write().catch(async (error) => {
762
+ if (error.code !== "ENOENT") throw error;
763
+ await mkdir(folder, { recursive: true });
764
+ await write();
765
+ });
766
+ const putBytes = async (bytes, mode) => {
767
+ const hash = createHash3("sha256").update(bytes).digest("hex");
768
+ if (sizes.has(hash)) return hash;
769
+ const temp = join2(dir, `incoming-${randomUUID3()}`);
770
+ try {
771
+ await withFolderMadeIfMissing(dir, () => writeFile(temp, bytes, { mode }));
772
+ await chmod(temp, mode & 4095);
773
+ const to = blobPath(dir, hash);
774
+ await withFolderMadeIfMissing(dirname(to), () => renameWithRetryAsync(temp, to));
775
+ } catch (error) {
776
+ await rm(temp, { force: true });
777
+ throw error;
778
+ }
779
+ file(hash, bytes.length);
780
+ return hash;
781
+ };
737
782
  return {
738
783
  async put(absPath) {
784
+ const small = await readIfSmall(absPath);
785
+ if (small) return putBytes(small.bytes, small.mode);
739
786
  const temp = join2(dir, `incoming-${randomUUID3()}`);
740
787
  await mkdir(dir, { recursive: true });
741
788
  await cloneOrStream(absPath, temp).catch(async (error) => {
@@ -750,8 +797,7 @@ async function openBlobStore(dir) {
750
797
  await mkdir(dirname(blobPath(dir, hash)), { recursive: true });
751
798
  const size = (await stat(temp)).size;
752
799
  await renameWithRetryAsync(temp, blobPath(dir, hash));
753
- sizes.set(hash, size);
754
- total += size;
800
+ file(hash, size);
755
801
  return hash;
756
802
  },
757
803
  reference: (absPath) => hashFile(absPath),
@@ -1825,6 +1871,20 @@ var OUTSIDE = { kind: "outside", name: "Outside" };
1825
1871
  var OUTSIDE_LABEL = "Changed outside the app";
1826
1872
  var RACY_MS = 2e3;
1827
1873
  var statKey = (file, sweptAt) => sweptAt - Math.max(file.mtimeMs, file.ctimeMs) < RACY_MS ? "" : `${file.size}:${file.mtimeMs}:${file.ctimeMs}`;
1874
+ var FIRST_COPY_SLOTS_IN_PROCESS = 4;
1875
+ var firstCopiesRunning = 0;
1876
+ var firstCopiesWaiting = [];
1877
+ async function inFirstCopySlot(copy) {
1878
+ if (firstCopiesRunning < FIRST_COPY_SLOTS_IN_PROCESS) firstCopiesRunning += 1;
1879
+ else await new Promise((yours) => firstCopiesWaiting.push(yours));
1880
+ try {
1881
+ return await copy();
1882
+ } finally {
1883
+ const next = firstCopiesWaiting.shift();
1884
+ if (next) next();
1885
+ else firstCopiesRunning -= 1;
1886
+ }
1887
+ }
1828
1888
  var MEDIA_LEDGER = ".media/manifest.jsonl";
1829
1889
  var KEPT_HIDDEN_PATHS = /* @__PURE__ */ new Set([...STUDIO_SIGNATURE_MANIFEST_PATHS, MEDIA_LEDGER]);
1830
1890
  function isHistoryPath(path) {
@@ -2061,7 +2121,7 @@ var Engine = class {
2061
2121
  async storeBytes(bytes) {
2062
2122
  const staged = join8(this.home, `overwrote-${randomUUID7()}`);
2063
2123
  await mkdir2(this.home, { recursive: true });
2064
- await writeFile(staged, bytes);
2124
+ await writeFile2(staged, bytes);
2065
2125
  try {
2066
2126
  await this.blobs.put(staged);
2067
2127
  } finally {
@@ -2090,11 +2150,17 @@ var Engine = class {
2090
2150
  async firstOpen() {
2091
2151
  const sweptAt = this.now();
2092
2152
  const files = historyFiles(this.dir);
2093
- for (const file of files.filter((file2) => file2.size <= COPY_LATER_ABOVE_BYTES)) {
2094
- const hash = await this.storeIfPresent(file.path);
2095
- if (this.whereFolder() !== "here") throw this.replaced();
2096
- if (hash !== null) this.tracked.set(file.path, { hash, stat: statKey(file, sweptAt) });
2097
- }
2153
+ const copied = files.filter((file) => file.size <= COPY_LATER_ABOVE_BYTES);
2154
+ const hashes = await Promise.allSettled(
2155
+ copied.map((file) => inFirstCopySlot(() => this.storeIfPresent(file.path)))
2156
+ );
2157
+ if (this.whereFolder() !== "here") throw this.replaced();
2158
+ copied.forEach((file, i) => {
2159
+ const result = hashes[i];
2160
+ if (result.status === "rejected") throw result.reason;
2161
+ if (result.value !== null)
2162
+ this.tracked.set(file.path, { hash: result.value, stat: statKey(file, sweptAt) });
2163
+ });
2098
2164
  this.log.baseline = this.manifest();
2099
2165
  this.log.keepsLedger = true;
2100
2166
  this.persistLog();
@@ -24,7 +24,7 @@ import {
24
24
  recordRenderSuccess,
25
25
  registerRenderOutput,
26
26
  retainSnapshotOutput
27
- } from "../chunk-Z6G2NBCQ.js";
27
+ } from "../chunk-LVEVVTIB.js";
28
28
  import "../chunk-QF7MFJJU.js";
29
29
  import "../chunk-SVQFE44C.js";
30
30
  import "../chunk-L3HJVTQE.js";
package/dist/index.js CHANGED
@@ -71,7 +71,7 @@ import {
71
71
  retainSnapshotOutput,
72
72
  serveWhileOpen,
73
73
  settledFileTag
74
- } from "./chunk-Z6G2NBCQ.js";
74
+ } from "./chunk-LVEVVTIB.js";
75
75
  import {
76
76
  clearElementScreenshotIsolation,
77
77
  getElementScreenshotClip
@@ -849,13 +849,13 @@ import {
849
849
  writeFileSync,
850
850
  mkdirSync,
851
851
  lstatSync,
852
- mkdtempSync,
853
852
  renameSync,
854
853
  rmSync,
855
854
  statSync
856
855
  } from "fs";
857
856
  import { dirname as dirname2, join as join3 } from "path";
858
857
  import { findFfBinary } from "@hyperframes/parsers/ff-binaries";
858
+ import { createStagingDir } from "@hyperframes/core/atomic-file";
859
859
  var SAMPLE_RATE = 4e3;
860
860
  var PEAK_COUNT = 4e3;
861
861
  var WAVEFORM_CACHE_VERSION = "v2";
@@ -947,7 +947,7 @@ function writeWaveformCache(cachePath, peaks) {
947
947
  if (!isWaveformCacheDirectory(cacheDir)) {
948
948
  throw new Error("Waveform cache must be a directory, not a symlink");
949
949
  }
950
- const stagingDir = mkdtempSync(join3(cacheDir, ".waveform-"));
950
+ const stagingDir = createStagingDir(cacheDir, ".waveform-");
951
951
  try {
952
952
  const stagingPath = join3(stagingDir, "peaks.json");
953
953
  writeFileSync(stagingPath, JSON.stringify(peaks), { flag: "wx" });
@@ -962,7 +962,7 @@ function writeWaveformCache(cachePath, peaks) {
962
962
 
963
963
  // src/helpers/mediaValidation.ts
964
964
  import { spawnSync } from "child_process";
965
- import { mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
965
+ import { mkdtempSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
966
966
  import { tmpdir } from "os";
967
967
  import { basename, join as join4 } from "path";
968
968
  import { FFPROBE_PATH_ENV, findFfBinary as findFfBinary2 } from "@hyperframes/parsers/ff-binaries";
@@ -1016,7 +1016,7 @@ function checkStreams(stdout, isVideo, isAudio) {
1016
1016
  }
1017
1017
  }
1018
1018
  function validateUploadedMediaBuffer(fileName, buffer, runner = defaultRunner) {
1019
- const tempDir = mkdtempSync2(join4(tmpdir(), "hyperframes-upload-"));
1019
+ const tempDir = mkdtempSync(join4(tmpdir(), "hyperframes-upload-"));
1020
1020
  const tempPath = join4(tempDir, basename(fileName));
1021
1021
  try {
1022
1022
  writeFileSync2(tempPath, buffer);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/studio-server",
3
- "version": "0.9.25",
3
+ "version": "0.9.27",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/heygen-com/hyperframes",
@@ -88,8 +88,8 @@
88
88
  "linkedom": "^0.18.12",
89
89
  "postcss": "^8.5.8",
90
90
  "postcss-selector-parser": "^7.1.2",
91
- "@hyperframes/core": "0.9.25",
92
- "@hyperframes/parsers": "0.9.25"
91
+ "@hyperframes/core": "0.9.27",
92
+ "@hyperframes/parsers": "0.9.27"
93
93
  },
94
94
  "devDependencies": {
95
95
  "@types/node": "^25.0.10",