@hyperframes/studio-server 0.7.102 → 0.7.104
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/index.d.ts +2 -2
- package/dist/index.js +71 -29
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -34,8 +34,8 @@ interface FileWriteReceipt {
|
|
|
34
34
|
}
|
|
35
35
|
/** Strong content version used as both the JSON version and HTTP ETag. */
|
|
36
36
|
declare function fileContentVersion(content: string): string;
|
|
37
|
-
/** Attach one API write's identity to the
|
|
38
|
-
declare function consumeFileWriteReceipt(absPath: string): FileWriteReceipt | null;
|
|
37
|
+
/** Attach one API write's identity to the watcher echo for its exact bytes. */
|
|
38
|
+
declare function consumeFileWriteReceipt(absPath: string, expectedVersion: string): FileWriteReceipt | null;
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* Build a standalone HTML page for a sub-composition.
|
package/dist/index.js
CHANGED
|
@@ -624,12 +624,13 @@ function recordFileWriteReceipt(absPath, receipt) {
|
|
|
624
624
|
current.push({ ...receipt, recordedAt: now });
|
|
625
625
|
receipts.set(absPath, current);
|
|
626
626
|
}
|
|
627
|
-
function consumeFileWriteReceipt(absPath) {
|
|
627
|
+
function consumeFileWriteReceipt(absPath, expectedVersion) {
|
|
628
628
|
const now = Date.now();
|
|
629
629
|
const current = (receipts.get(absPath) ?? []).filter(
|
|
630
630
|
(entry) => now - entry.recordedAt < RECEIPT_TTL_MS
|
|
631
631
|
);
|
|
632
|
-
const
|
|
632
|
+
const receiptIndex = current.findIndex((entry) => entry.version === expectedVersion);
|
|
633
|
+
const receipt = receiptIndex === -1 ? null : current.splice(receiptIndex, 1)[0] ?? null;
|
|
633
634
|
if (current.length > 0) receipts.set(absPath, current);
|
|
634
635
|
else receipts.delete(absPath);
|
|
635
636
|
if (!receipt) return null;
|
|
@@ -1077,19 +1078,44 @@ function commitElementPatchBatches(projectDir, batches, writeFile = writeFileSyn
|
|
|
1077
1078
|
}
|
|
1078
1079
|
return { durable: true, files };
|
|
1079
1080
|
}
|
|
1081
|
+
function commitElementPatchBatchesWithReceipts(c, projectDir, batches) {
|
|
1082
|
+
const result = commitElementPatchBatches(projectDir, batches);
|
|
1083
|
+
if ("error" in result || !result.durable) return result;
|
|
1084
|
+
for (const file of result.files) {
|
|
1085
|
+
if (!file.changed) continue;
|
|
1086
|
+
const absPath = resolveWithinProject(projectDir, file.sourceFile);
|
|
1087
|
+
if (!absPath) throw new Error(`Committed element patch escaped project: ${file.sourceFile}`);
|
|
1088
|
+
recordMutationReceipt(c, file.sourceFile, absPath, file.after);
|
|
1089
|
+
}
|
|
1090
|
+
return result;
|
|
1091
|
+
}
|
|
1092
|
+
function recordMutationReceipt(c, filePath, absPath, html) {
|
|
1093
|
+
const version = fileContentVersion(html);
|
|
1094
|
+
const writeToken = createWriteToken(c.req.header("X-Hyperframes-Write-Token"));
|
|
1095
|
+
recordFileWriteReceipt(absPath, { path: filePath, version, writeToken });
|
|
1096
|
+
return { version, writeToken };
|
|
1097
|
+
}
|
|
1098
|
+
function writeFileWithReceipt(c, filePath, absPath, html) {
|
|
1099
|
+
writeFileSync4(absPath, html, "utf-8");
|
|
1100
|
+
return recordMutationReceipt(c, filePath, absPath, html);
|
|
1101
|
+
}
|
|
1102
|
+
function writeMutationResult(c, projectDir, filePath, absPath, html) {
|
|
1103
|
+
const backup = snapshotBeforeWrite(projectDir, absPath);
|
|
1104
|
+
if (backup.error) console.warn(`Failed to create backup for ${filePath}: ${backup.error}`);
|
|
1105
|
+
const { version } = writeFileWithReceipt(c, filePath, absPath, html);
|
|
1106
|
+
return { backupPath: backupPathForResponse(projectDir, backup.backupPath), version };
|
|
1107
|
+
}
|
|
1080
1108
|
function writeIfChanged(c, projectDir, filePath, absPath, original, next) {
|
|
1081
1109
|
if (next === original) {
|
|
1082
1110
|
return c.json({ ok: true, changed: false, content: original, path: filePath });
|
|
1083
1111
|
}
|
|
1084
|
-
const
|
|
1085
|
-
if (backup.error) console.warn(`Failed to create backup for ${filePath}: ${backup.error}`);
|
|
1086
|
-
writeFileSync4(absPath, next, "utf-8");
|
|
1112
|
+
const { backupPath } = writeMutationResult(c, projectDir, filePath, absPath, next);
|
|
1087
1113
|
return c.json({
|
|
1088
1114
|
ok: true,
|
|
1089
1115
|
changed: true,
|
|
1090
1116
|
content: next,
|
|
1091
1117
|
path: filePath,
|
|
1092
|
-
backupPath
|
|
1118
|
+
backupPath
|
|
1093
1119
|
});
|
|
1094
1120
|
}
|
|
1095
1121
|
function rejectUnsafeMutationValues(c, unsafeFields) {
|
|
@@ -1488,10 +1514,13 @@ async function applyGsapMutations(c, res, mutations) {
|
|
|
1488
1514
|
return c.json({ error: "file changed during GSAP mutation", conflict: true }, 409);
|
|
1489
1515
|
}
|
|
1490
1516
|
if (changed) {
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1517
|
+
backupPath = writeMutationResult(
|
|
1518
|
+
c,
|
|
1519
|
+
res.project.dir,
|
|
1520
|
+
res.filePath,
|
|
1521
|
+
res.absPath,
|
|
1522
|
+
newHtml
|
|
1523
|
+
).backupPath;
|
|
1495
1524
|
}
|
|
1496
1525
|
const responsePayload = {
|
|
1497
1526
|
ok: true,
|
|
@@ -2448,10 +2477,12 @@ function registerFileRoutes(api, adapter) {
|
|
|
2448
2477
|
}
|
|
2449
2478
|
const backup = snapshotBeforeWrite(ctx.project.dir, ctx.absPath);
|
|
2450
2479
|
if (backup.error) return c.json({ error: `backup failed: ${backup.error}` }, 500);
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2480
|
+
const { version, writeToken } = writeFileWithReceipt(
|
|
2481
|
+
c,
|
|
2482
|
+
ctx.filePath,
|
|
2483
|
+
ctx.absPath,
|
|
2484
|
+
insertion.html
|
|
2485
|
+
);
|
|
2455
2486
|
c.header("ETag", version);
|
|
2456
2487
|
return c.json({
|
|
2457
2488
|
ok: true,
|
|
@@ -2649,10 +2680,13 @@ function registerFileRoutes(api, adapter) {
|
|
|
2649
2680
|
version: version2
|
|
2650
2681
|
});
|
|
2651
2682
|
}
|
|
2652
|
-
const
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2683
|
+
const { version, backupPath } = writeMutationResult(
|
|
2684
|
+
c,
|
|
2685
|
+
ctx.project.dir,
|
|
2686
|
+
ctx.filePath,
|
|
2687
|
+
ctx.absPath,
|
|
2688
|
+
result.html
|
|
2689
|
+
);
|
|
2656
2690
|
c.header("ETag", version);
|
|
2657
2691
|
return c.json({
|
|
2658
2692
|
ok: true,
|
|
@@ -2661,7 +2695,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
2661
2695
|
newId: result.newId,
|
|
2662
2696
|
path: ctx.filePath,
|
|
2663
2697
|
version,
|
|
2664
|
-
backupPath
|
|
2698
|
+
backupPath
|
|
2665
2699
|
});
|
|
2666
2700
|
});
|
|
2667
2701
|
api.post("/projects/:id/file-mutations/patch-element/*", async (c) => {
|
|
@@ -2696,16 +2730,20 @@ function registerFileRoutes(api, adapter) {
|
|
|
2696
2730
|
path: ctx.filePath
|
|
2697
2731
|
});
|
|
2698
2732
|
}
|
|
2699
|
-
const
|
|
2700
|
-
|
|
2701
|
-
|
|
2733
|
+
const { backupPath } = writeMutationResult(
|
|
2734
|
+
c,
|
|
2735
|
+
ctx.project.dir,
|
|
2736
|
+
ctx.filePath,
|
|
2737
|
+
ctx.absPath,
|
|
2738
|
+
patched
|
|
2739
|
+
);
|
|
2702
2740
|
return c.json({
|
|
2703
2741
|
ok: true,
|
|
2704
2742
|
changed: true,
|
|
2705
2743
|
matched,
|
|
2706
2744
|
content: patched,
|
|
2707
2745
|
path: ctx.filePath,
|
|
2708
|
-
backupPath
|
|
2746
|
+
backupPath
|
|
2709
2747
|
});
|
|
2710
2748
|
});
|
|
2711
2749
|
api.post("/projects/:id/file-mutations/patch-element-batches", async (c) => {
|
|
@@ -2717,7 +2755,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
2717
2755
|
}
|
|
2718
2756
|
const unsafeFields = findUnsafeElementPatchBatchValues(body.batches);
|
|
2719
2757
|
if (unsafeFields.length > 0) return rejectUnsafeMutationValues(c, unsafeFields);
|
|
2720
|
-
const result =
|
|
2758
|
+
const result = commitElementPatchBatchesWithReceipts(c, project.dir, body.batches);
|
|
2721
2759
|
if ("error" in result) {
|
|
2722
2760
|
return elementPatchBatchCommitErrorResponse(c, result.error, result.sourceFile);
|
|
2723
2761
|
}
|
|
@@ -2735,7 +2773,7 @@ function registerFileRoutes(api, adapter) {
|
|
|
2735
2773
|
if (unsafeFields.length > 0) {
|
|
2736
2774
|
return rejectUnsafeMutationValues(c, unsafeFields);
|
|
2737
2775
|
}
|
|
2738
|
-
const result =
|
|
2776
|
+
const result = commitElementPatchBatchesWithReceipts(c, ctx.project.dir, [batch]);
|
|
2739
2777
|
if ("error" in result) {
|
|
2740
2778
|
return elementPatchBatchCommitErrorResponse(c, result.error, result.sourceFile);
|
|
2741
2779
|
}
|
|
@@ -2791,16 +2829,20 @@ function registerFileRoutes(api, adapter) {
|
|
|
2791
2829
|
result.error === "grouped elements must share a single parent" ? 422 : 400
|
|
2792
2830
|
);
|
|
2793
2831
|
}
|
|
2794
|
-
const
|
|
2795
|
-
|
|
2796
|
-
|
|
2832
|
+
const { backupPath } = writeMutationResult(
|
|
2833
|
+
c,
|
|
2834
|
+
ctx.project.dir,
|
|
2835
|
+
ctx.filePath,
|
|
2836
|
+
ctx.absPath,
|
|
2837
|
+
result.html
|
|
2838
|
+
);
|
|
2797
2839
|
return c.json({
|
|
2798
2840
|
ok: true,
|
|
2799
2841
|
changed: true,
|
|
2800
2842
|
groupId: result.groupId,
|
|
2801
2843
|
content: result.html,
|
|
2802
2844
|
path: ctx.filePath,
|
|
2803
|
-
backupPath
|
|
2845
|
+
backupPath
|
|
2804
2846
|
});
|
|
2805
2847
|
});
|
|
2806
2848
|
api.post("/projects/:id/file-mutations/unwrap-elements/*", async (c) => {
|