@hyperframes/producer 0.2.3 → 0.2.5
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.
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +5 -5
- package/dist/index.js +200 -30
- package/dist/index.js.map +4 -4
- package/dist/public-server.js +200 -30
- package/dist/public-server.js.map +4 -4
- package/dist/server.d.ts +4 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/services/renderOrchestrator.d.ts.map +1 -1
- package/dist/utils/semaphore.d.ts +16 -0
- package/dist/utils/semaphore.d.ts.map +1 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -99285,7 +99285,8 @@ function truncateSnippet(value, maxLength = 220) {
|
|
|
99285
99285
|
|
|
99286
99286
|
// ../core/src/lint/context.ts
|
|
99287
99287
|
function buildLintContext(html, options = {}) {
|
|
99288
|
-
|
|
99288
|
+
const rawSource = html || "";
|
|
99289
|
+
let source2 = rawSource;
|
|
99289
99290
|
const templateMatch = source2.match(/<template[^>]*>([\s\S]*)<\/template>/i);
|
|
99290
99291
|
if (templateMatch?.[1]) source2 = templateMatch[1];
|
|
99291
99292
|
const tags = extractOpenTags(source2);
|
|
@@ -99296,6 +99297,7 @@ function buildLintContext(html, options = {}) {
|
|
|
99296
99297
|
const rootCompositionId = readAttr(rootTag?.raw || "", "data-composition-id");
|
|
99297
99298
|
return {
|
|
99298
99299
|
source: source2,
|
|
99300
|
+
rawSource,
|
|
99299
99301
|
tags,
|
|
99300
99302
|
styles,
|
|
99301
99303
|
scripts,
|
|
@@ -100086,8 +100088,30 @@ ${right2.raw}`)
|
|
|
100086
100088
|
findings.push({
|
|
100087
100089
|
code: "gsap_infinite_repeat",
|
|
100088
100090
|
severity: "error",
|
|
100089
|
-
message: "GSAP tween uses `repeat: -1` (infinite). Infinite repeats break the deterministic capture engine which seeks to exact frame times. Use a finite repeat count calculated from the composition duration: `repeat: Math.
|
|
100090
|
-
fixHint: "Replace `repeat: -1` with a finite count, e.g. `repeat: Math.
|
|
100091
|
+
message: "GSAP tween uses `repeat: -1` (infinite). Infinite repeats break the deterministic capture engine which seeks to exact frame times. Use a finite repeat count calculated from the composition duration: `repeat: Math.floor(duration / cycleDuration) - 1`.",
|
|
100092
|
+
fixHint: "Replace `repeat: -1` with a finite count, e.g. `repeat: Math.floor(totalDuration / singleCycleDuration) - 1`. Use Math.floor (not Math.ceil) to ensure the animation fits within the total duration.",
|
|
100093
|
+
snippet: truncateSnippet(snippet)
|
|
100094
|
+
});
|
|
100095
|
+
}
|
|
100096
|
+
}
|
|
100097
|
+
return findings;
|
|
100098
|
+
},
|
|
100099
|
+
// gsap_repeat_ceil_overshoot
|
|
100100
|
+
({ scripts }) => {
|
|
100101
|
+
const findings = [];
|
|
100102
|
+
for (const script of scripts) {
|
|
100103
|
+
const content = script.content;
|
|
100104
|
+
const pattern = /repeat\s*:\s*Math\.ceil\s*\([^)]+\)\s*-\s*1/g;
|
|
100105
|
+
let match2;
|
|
100106
|
+
while ((match2 = pattern.exec(content)) !== null) {
|
|
100107
|
+
const contextStart = Math.max(0, match2.index - 40);
|
|
100108
|
+
const contextEnd = Math.min(content.length, match2.index + match2[0].length + 40);
|
|
100109
|
+
const snippet = content.slice(contextStart, contextEnd).trim();
|
|
100110
|
+
findings.push({
|
|
100111
|
+
code: "gsap_repeat_ceil_overshoot",
|
|
100112
|
+
severity: "warning",
|
|
100113
|
+
message: "GSAP repeat calculation uses `Math.ceil` which can overshoot the composition duration. For example, Math.ceil(10.5 / 2) - 1 = 5 repeats \u2192 6 cycles \xD7 2s = 12s, exceeding 10.5s.",
|
|
100114
|
+
fixHint: "Use `Math.floor` instead of `Math.ceil` to ensure the animation fits within the duration: `repeat: Math.floor(totalDuration / cycleDuration) - 1`. Math.floor(10.5 / 2) - 1 = 4 repeats \u2192 5 cycles \xD7 2s = 10s \u2713",
|
|
100091
100115
|
snippet: truncateSnippet(snippet)
|
|
100092
100116
|
});
|
|
100093
100117
|
}
|
|
@@ -100469,6 +100493,76 @@ var compositionRules = [
|
|
|
100469
100493
|
}
|
|
100470
100494
|
return findings;
|
|
100471
100495
|
},
|
|
100496
|
+
// root_composition_missing_data_start
|
|
100497
|
+
({ rootTag }) => {
|
|
100498
|
+
const findings = [];
|
|
100499
|
+
if (!rootTag) return findings;
|
|
100500
|
+
const compId = readAttr(rootTag.raw, "data-composition-id");
|
|
100501
|
+
if (!compId) return findings;
|
|
100502
|
+
const hasStart = readAttr(rootTag.raw, "data-start") !== null;
|
|
100503
|
+
if (!hasStart) {
|
|
100504
|
+
findings.push({
|
|
100505
|
+
code: "root_composition_missing_data_start",
|
|
100506
|
+
severity: "warning",
|
|
100507
|
+
message: `Root composition "${compId}" is missing data-start. The runtime needs data-start="0" on the root element to begin playback.`,
|
|
100508
|
+
fixHint: 'Add data-start="0" to the root composition element.',
|
|
100509
|
+
snippet: truncateSnippet(rootTag.raw)
|
|
100510
|
+
});
|
|
100511
|
+
}
|
|
100512
|
+
return findings;
|
|
100513
|
+
},
|
|
100514
|
+
// root_composition_missing_data_duration
|
|
100515
|
+
({ rootTag }) => {
|
|
100516
|
+
const findings = [];
|
|
100517
|
+
if (!rootTag) return findings;
|
|
100518
|
+
const compId = readAttr(rootTag.raw, "data-composition-id");
|
|
100519
|
+
if (!compId) return findings;
|
|
100520
|
+
const hasDuration = readAttr(rootTag.raw, "data-duration") !== null;
|
|
100521
|
+
if (!hasDuration) {
|
|
100522
|
+
findings.push({
|
|
100523
|
+
code: "root_composition_missing_data_duration",
|
|
100524
|
+
severity: "warning",
|
|
100525
|
+
message: `Root composition "${compId}" is missing data-duration. Without an explicit duration, the runtime may infer Infinity for compositions with repeating animations, causing playback issues.`,
|
|
100526
|
+
fixHint: 'Add data-duration="X" to the root composition element, where X is the total duration in seconds.',
|
|
100527
|
+
snippet: truncateSnippet(rootTag.raw)
|
|
100528
|
+
});
|
|
100529
|
+
}
|
|
100530
|
+
return findings;
|
|
100531
|
+
},
|
|
100532
|
+
// standalone_composition_wrapped_in_template
|
|
100533
|
+
({ rawSource, options }) => {
|
|
100534
|
+
const findings = [];
|
|
100535
|
+
if (options.isSubComposition) return findings;
|
|
100536
|
+
const trimmed = rawSource.trimStart().toLowerCase();
|
|
100537
|
+
if (trimmed.startsWith("<template")) {
|
|
100538
|
+
findings.push({
|
|
100539
|
+
code: "standalone_composition_wrapped_in_template",
|
|
100540
|
+
severity: "warning",
|
|
100541
|
+
message: "Root index.html is wrapped in a <template> tag. Only sub-compositions loaded via data-composition-src should use <template> wrappers. The runtime cannot play a standalone composition inside a template.",
|
|
100542
|
+
fixHint: "Remove the <template> wrapper. Use <!DOCTYPE html><html>...<div data-composition-id>...</div>...</html> instead."
|
|
100543
|
+
});
|
|
100544
|
+
}
|
|
100545
|
+
return findings;
|
|
100546
|
+
},
|
|
100547
|
+
// root_composition_missing_html_wrapper
|
|
100548
|
+
({ rawSource, rootTag, options }) => {
|
|
100549
|
+
const findings = [];
|
|
100550
|
+
if (options.isSubComposition) return findings;
|
|
100551
|
+
const trimmed = rawSource.trimStart().toLowerCase();
|
|
100552
|
+
if (trimmed.startsWith("<template")) return findings;
|
|
100553
|
+
const hasDoctype = trimmed.startsWith("<!doctype") || trimmed.startsWith("<html");
|
|
100554
|
+
const hasComposition = rawSource.includes("data-composition-id");
|
|
100555
|
+
if (hasComposition && !hasDoctype) {
|
|
100556
|
+
findings.push({
|
|
100557
|
+
code: "root_composition_missing_html_wrapper",
|
|
100558
|
+
severity: "error",
|
|
100559
|
+
message: "Composition starts with a bare element instead of a proper HTML document. An index.html that contains data-composition-id but no <!DOCTYPE html>, <html>, or <body> is a fragment \u2014 browsers quirks-mode it, the preview server cannot load it, and the bundler will fail to inject runtime scripts.",
|
|
100560
|
+
fixHint: 'Wrap the composition in <!DOCTYPE html><html><head><meta charset="UTF-8"></head><body>...</body></html>.',
|
|
100561
|
+
snippet: rootTag ? truncateSnippet(rootTag.raw) : void 0
|
|
100562
|
+
});
|
|
100563
|
+
}
|
|
100564
|
+
return findings;
|
|
100565
|
+
},
|
|
100472
100566
|
// requestanimationframe_in_composition
|
|
100473
100567
|
({ scripts }) => {
|
|
100474
100568
|
const findings = [];
|
|
@@ -100650,12 +100744,12 @@ function quantizeTimeToFrame(timeSeconds, fps) {
|
|
|
100650
100744
|
return frameIndex / safeFps;
|
|
100651
100745
|
}
|
|
100652
100746
|
|
|
100653
|
-
// ../../node_modules/.bun/@chenglou+pretext@0.0.
|
|
100747
|
+
// ../../node_modules/.bun/@chenglou+pretext@0.0.5/node_modules/@chenglou/pretext/dist/analysis.js
|
|
100654
100748
|
var arabicScriptRe = new RegExp("\\p{Script=Arabic}", "u");
|
|
100655
100749
|
var combiningMarkRe = new RegExp("\\p{M}", "u");
|
|
100656
100750
|
var decimalDigitRe = new RegExp("\\p{Nd}", "u");
|
|
100657
100751
|
|
|
100658
|
-
// ../../node_modules/.bun/@chenglou+pretext@0.0.
|
|
100752
|
+
// ../../node_modules/.bun/@chenglou+pretext@0.0.5/node_modules/@chenglou/pretext/dist/measurement.js
|
|
100659
100753
|
var emojiPresentationRe = new RegExp("\\p{Emoji_Presentation}", "u");
|
|
100660
100754
|
|
|
100661
100755
|
// ../engine/src/services/screenshotService.ts
|
|
@@ -100669,18 +100763,36 @@ async function getCdpSession(page) {
|
|
|
100669
100763
|
return client;
|
|
100670
100764
|
}
|
|
100671
100765
|
var lastFrameCache = /* @__PURE__ */ new WeakMap();
|
|
100766
|
+
var PENDING_FRAME_RETRIES = 5;
|
|
100767
|
+
async function sendBeginFrame(client, params) {
|
|
100768
|
+
for (let attempt = 0; ; attempt++) {
|
|
100769
|
+
try {
|
|
100770
|
+
return await client.send("HeadlessExperimental.beginFrame", params);
|
|
100771
|
+
} catch (err) {
|
|
100772
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
100773
|
+
const isPending = msg.includes("Another frame is pending");
|
|
100774
|
+
if (isPending && attempt < PENDING_FRAME_RETRIES) {
|
|
100775
|
+
await new Promise((r) => setTimeout(r, 50 * 2 ** attempt));
|
|
100776
|
+
continue;
|
|
100777
|
+
}
|
|
100778
|
+
if (isPending) {
|
|
100779
|
+
throw new Error(
|
|
100780
|
+
`[BeginFrame] Frame still pending after ${PENDING_FRAME_RETRIES} retries \u2014 CPU overloaded by parallel renders. Reduce concurrent renders or use --docker for isolation.`
|
|
100781
|
+
);
|
|
100782
|
+
}
|
|
100783
|
+
throw err;
|
|
100784
|
+
}
|
|
100785
|
+
}
|
|
100786
|
+
}
|
|
100672
100787
|
async function beginFrameCapture(page, options, frameTimeTicks, interval) {
|
|
100673
100788
|
const client = await getCdpSession(page);
|
|
100674
|
-
const
|
|
100675
|
-
const
|
|
100676
|
-
|
|
100677
|
-
|
|
100678
|
-
|
|
100679
|
-
|
|
100680
|
-
|
|
100681
|
-
optimizeForSpeed: true
|
|
100682
|
-
}
|
|
100683
|
-
});
|
|
100789
|
+
const isPng = options.format === "png";
|
|
100790
|
+
const screenshot = {
|
|
100791
|
+
format: isPng ? "png" : "jpeg",
|
|
100792
|
+
quality: isPng ? void 0 : options.quality ?? 80,
|
|
100793
|
+
optimizeForSpeed: true
|
|
100794
|
+
};
|
|
100795
|
+
const result = await sendBeginFrame(client, { frameTimeTicks, interval, screenshot });
|
|
100684
100796
|
let buffer;
|
|
100685
100797
|
if (result.screenshotData) {
|
|
100686
100798
|
buffer = Buffer.from(result.screenshotData, "base64");
|
|
@@ -100690,16 +100802,12 @@ async function beginFrameCapture(page, options, frameTimeTicks, interval) {
|
|
|
100690
100802
|
if (cached) {
|
|
100691
100803
|
buffer = cached;
|
|
100692
100804
|
} else {
|
|
100693
|
-
const
|
|
100805
|
+
const fallback = await sendBeginFrame(client, {
|
|
100694
100806
|
frameTimeTicks: frameTimeTicks + 1e-3,
|
|
100695
100807
|
interval,
|
|
100696
|
-
screenshot
|
|
100697
|
-
format: format3,
|
|
100698
|
-
quality: format3 === "jpeg" ? options.quality ?? 80 : void 0,
|
|
100699
|
-
optimizeForSpeed: true
|
|
100700
|
-
}
|
|
100808
|
+
screenshot
|
|
100701
100809
|
});
|
|
100702
|
-
buffer =
|
|
100810
|
+
buffer = fallback.screenshotData ? Buffer.from(fallback.screenshotData, "base64") : Buffer.alloc(0);
|
|
100703
100811
|
if (buffer.length > 0) lastFrameCache.set(page, buffer);
|
|
100704
100812
|
}
|
|
100705
100813
|
}
|
|
@@ -102202,7 +102310,7 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
|
|
|
102202
102310
|
});
|
|
102203
102311
|
});
|
|
102204
102312
|
}
|
|
102205
|
-
async function extractAllVideoFrames(videos, baseDir, options, signal, config2) {
|
|
102313
|
+
async function extractAllVideoFrames(videos, baseDir, options, signal, config2, compiledDir) {
|
|
102206
102314
|
const startTime = Date.now();
|
|
102207
102315
|
const extracted = [];
|
|
102208
102316
|
const errors = [];
|
|
@@ -102215,7 +102323,8 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config2)
|
|
|
102215
102323
|
try {
|
|
102216
102324
|
let videoPath = video.src;
|
|
102217
102325
|
if (!videoPath.startsWith("/") && !isHttpUrl(videoPath)) {
|
|
102218
|
-
|
|
102326
|
+
const fromCompiled = compiledDir ? join8(compiledDir, videoPath) : null;
|
|
102327
|
+
videoPath = fromCompiled && existsSync8(fromCompiled) ? fromCompiled : join8(baseDir, videoPath);
|
|
102219
102328
|
}
|
|
102220
102329
|
if (isHttpUrl(videoPath)) {
|
|
102221
102330
|
const downloadDir = join8(options.outputDir, "_downloads");
|
|
@@ -102676,7 +102785,7 @@ async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config2
|
|
|
102676
102785
|
tracksProcessed: tracks.length
|
|
102677
102786
|
};
|
|
102678
102787
|
}
|
|
102679
|
-
async function processCompositionAudio(elements, baseDir, workDir, outputPath, totalDuration, signal, config2) {
|
|
102788
|
+
async function processCompositionAudio(elements, baseDir, workDir, outputPath, totalDuration, signal, config2, compiledDir) {
|
|
102680
102789
|
const startMs = Date.now();
|
|
102681
102790
|
const tracks = [];
|
|
102682
102791
|
const errors = [];
|
|
@@ -102690,7 +102799,8 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
|
|
|
102690
102799
|
try {
|
|
102691
102800
|
let srcPath = element.src;
|
|
102692
102801
|
if (!srcPath.startsWith("/") && !isHttpUrl(srcPath)) {
|
|
102693
|
-
|
|
102802
|
+
const fromCompiled = compiledDir ? join9(compiledDir, srcPath) : null;
|
|
102803
|
+
srcPath = fromCompiled && existsSync9(fromCompiled) ? fromCompiled : join9(baseDir, srcPath);
|
|
102694
102804
|
}
|
|
102695
102805
|
if (isHttpUrl(srcPath)) {
|
|
102696
102806
|
try {
|
|
@@ -102703,7 +102813,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
|
|
|
102703
102813
|
}
|
|
102704
102814
|
}
|
|
102705
102815
|
if (!existsSync9(srcPath)) {
|
|
102706
|
-
errors.push(`Source not found: ${element.id}`);
|
|
102816
|
+
errors.push(`Source not found: ${element.id} (${element.src})`);
|
|
102707
102817
|
return;
|
|
102708
102818
|
}
|
|
102709
102819
|
if (element.end - element.start <= 0) {
|
|
@@ -107405,12 +107515,15 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
107405
107515
|
const stage2Start = Date.now();
|
|
107406
107516
|
updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress);
|
|
107407
107517
|
let frameLookup = null;
|
|
107518
|
+
const compiledDir = join13(workDir, "compiled");
|
|
107408
107519
|
if (composition.videos.length > 0) {
|
|
107409
107520
|
const extractionResult = await extractAllVideoFrames(
|
|
107410
107521
|
composition.videos,
|
|
107411
107522
|
projectDir,
|
|
107412
107523
|
{ fps: job.config.fps, outputDir: join13(workDir, "video-frames") },
|
|
107413
|
-
abortSignal
|
|
107524
|
+
abortSignal,
|
|
107525
|
+
void 0,
|
|
107526
|
+
compiledDir
|
|
107414
107527
|
);
|
|
107415
107528
|
assertNotAborted();
|
|
107416
107529
|
if (extractionResult.extracted.length > 0) {
|
|
@@ -107450,7 +107563,9 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
107450
107563
|
join13(workDir, "audio-work"),
|
|
107451
107564
|
audioOutputPath,
|
|
107452
107565
|
job.duration,
|
|
107453
|
-
abortSignal
|
|
107566
|
+
abortSignal,
|
|
107567
|
+
void 0,
|
|
107568
|
+
compiledDir
|
|
107454
107569
|
);
|
|
107455
107570
|
assertNotAborted();
|
|
107456
107571
|
hasAudio = audioResult.success;
|
|
@@ -108117,6 +108232,40 @@ function resolveRenderPaths(projectDir, outputPath, rendersDir = DEFAULT_RENDERS
|
|
|
108117
108232
|
return { absoluteProjectDir, absoluteOutputPath };
|
|
108118
108233
|
}
|
|
108119
108234
|
|
|
108235
|
+
// src/utils/semaphore.ts
|
|
108236
|
+
var Semaphore = class {
|
|
108237
|
+
constructor(maxConcurrent) {
|
|
108238
|
+
this.maxConcurrent = maxConcurrent;
|
|
108239
|
+
}
|
|
108240
|
+
queue = [];
|
|
108241
|
+
active = 0;
|
|
108242
|
+
async acquire() {
|
|
108243
|
+
if (this.active < this.maxConcurrent) {
|
|
108244
|
+
this.active++;
|
|
108245
|
+
return () => this.release();
|
|
108246
|
+
}
|
|
108247
|
+
return new Promise((resolve13) => {
|
|
108248
|
+
this.queue.push(() => {
|
|
108249
|
+
this.active++;
|
|
108250
|
+
resolve13(() => this.release());
|
|
108251
|
+
});
|
|
108252
|
+
});
|
|
108253
|
+
}
|
|
108254
|
+
release() {
|
|
108255
|
+
this.active--;
|
|
108256
|
+
const next = this.queue.shift();
|
|
108257
|
+
if (next) next();
|
|
108258
|
+
}
|
|
108259
|
+
/** Current number of active slots. */
|
|
108260
|
+
get activeCount() {
|
|
108261
|
+
return this.active;
|
|
108262
|
+
}
|
|
108263
|
+
/** Number of waiters in the queue. */
|
|
108264
|
+
get waitingCount() {
|
|
108265
|
+
return this.queue.length;
|
|
108266
|
+
}
|
|
108267
|
+
};
|
|
108268
|
+
|
|
108120
108269
|
// src/server.ts
|
|
108121
108270
|
function parseRenderOptions(body) {
|
|
108122
108271
|
const fps = [24, 30, 60].includes(body.fps) ? body.fps : 30;
|
|
@@ -108230,6 +108379,8 @@ function createRenderHandlers(options = {}) {
|
|
|
108230
108379
|
const rendersDir = options.rendersDir ?? process.env.PRODUCER_RENDERS_DIR ?? "/tmp";
|
|
108231
108380
|
const artifactTtlMs = options.artifactTtlMs ?? Number(process.env.PRODUCER_OUTPUT_ARTIFACT_TTL_MS || 15 * 60 * 1e3);
|
|
108232
108381
|
const store = createArtifactStore(artifactTtlMs);
|
|
108382
|
+
const maxConcurrentRenders = options.maxConcurrentRenders ?? Number(process.env.PRODUCER_MAX_CONCURRENT_RENDERS || 2);
|
|
108383
|
+
const renderSemaphore = new Semaphore(maxConcurrentRenders);
|
|
108233
108384
|
const startTime = Date.now();
|
|
108234
108385
|
const health = (c) => c.json({
|
|
108235
108386
|
status: "ok",
|
|
@@ -108286,6 +108437,7 @@ function createRenderHandlers(options = {}) {
|
|
|
108286
108437
|
);
|
|
108287
108438
|
const outputDir = dirname11(absoluteOutputPath);
|
|
108288
108439
|
if (!existsSync16(outputDir)) mkdirSync10(outputDir, { recursive: true });
|
|
108440
|
+
const release = await renderSemaphore.acquire();
|
|
108289
108441
|
log.info("render started", {
|
|
108290
108442
|
requestId,
|
|
108291
108443
|
projectDir: input2.projectDir,
|
|
@@ -108353,6 +108505,7 @@ function createRenderHandlers(options = {}) {
|
|
|
108353
108505
|
500
|
|
108354
108506
|
);
|
|
108355
108507
|
} finally {
|
|
108508
|
+
release();
|
|
108356
108509
|
cleanupTempDir(cleanupProjectDir, log);
|
|
108357
108510
|
}
|
|
108358
108511
|
};
|
|
@@ -108409,6 +108562,16 @@ function createRenderHandlers(options = {}) {
|
|
|
108409
108562
|
const abortController = new AbortController();
|
|
108410
108563
|
const onRequestAbort = () => abortController.abort(new RenderCancelledError("request_aborted"));
|
|
108411
108564
|
c.req.raw.signal.addEventListener("abort", onRequestAbort, { once: true });
|
|
108565
|
+
if (renderSemaphore.activeCount >= maxConcurrentRenders) {
|
|
108566
|
+
await stream2.writeSSE({
|
|
108567
|
+
data: JSON.stringify({
|
|
108568
|
+
type: "queued",
|
|
108569
|
+
requestId,
|
|
108570
|
+
position: renderSemaphore.waitingCount
|
|
108571
|
+
})
|
|
108572
|
+
});
|
|
108573
|
+
}
|
|
108574
|
+
const release = await renderSemaphore.acquire();
|
|
108412
108575
|
try {
|
|
108413
108576
|
await executeRenderJob(
|
|
108414
108577
|
job,
|
|
@@ -108476,6 +108639,7 @@ function createRenderHandlers(options = {}) {
|
|
|
108476
108639
|
})
|
|
108477
108640
|
});
|
|
108478
108641
|
} finally {
|
|
108642
|
+
release();
|
|
108479
108643
|
c.req.raw.signal.removeEventListener("abort", onRequestAbort);
|
|
108480
108644
|
cleanupTempDir(cleanupProjectDir, log);
|
|
108481
108645
|
}
|
|
@@ -108500,7 +108664,12 @@ function createRenderHandlers(options = {}) {
|
|
|
108500
108664
|
}
|
|
108501
108665
|
});
|
|
108502
108666
|
};
|
|
108503
|
-
|
|
108667
|
+
const queue = (c) => c.json({
|
|
108668
|
+
maxConcurrentRenders,
|
|
108669
|
+
activeRenders: renderSemaphore.activeCount,
|
|
108670
|
+
queuedRenders: renderSemaphore.waitingCount
|
|
108671
|
+
});
|
|
108672
|
+
return { render: render2, renderStream, lint, health, outputs, queue };
|
|
108504
108673
|
}
|
|
108505
108674
|
function createProducerApp(options = {}) {
|
|
108506
108675
|
const app = new Hono2();
|
|
@@ -108508,6 +108677,7 @@ function createProducerApp(options = {}) {
|
|
|
108508
108677
|
app.get("/health", handlers.health);
|
|
108509
108678
|
app.post("/render", handlers.render);
|
|
108510
108679
|
app.post("/render/stream", handlers.renderStream);
|
|
108680
|
+
app.get("/render/queue", handlers.queue);
|
|
108511
108681
|
app.post("/lint", handlers.lint);
|
|
108512
108682
|
app.get("/outputs/:token", handlers.outputs);
|
|
108513
108683
|
return app;
|