fix: restore inspector template hot reload

This commit is contained in:
Your Name 2026-08-06 16:37:42 -04:00
parent cbbb87cd2e
commit ffba038b35
7 changed files with 232 additions and 35 deletions

51
db.js
View file

@ -2,6 +2,7 @@ const fs = require("fs");
const path = require("path");
const betterSqlite3 = require("better-sqlite3");
const { LRUCache } = require("lru-cache");
const { randomUUID } = require("node:crypto");
const { Eta } = require("eta");
const cheerio = require("cheerio");
@ -95,8 +96,37 @@ function getAllRoutes() {
}
const templateCache = new LRUCache({ max: 100 });
const templateContextCache = new LRUCache({ max: 2000, ttl: 30 * 60 * 1000 });
function annotateTemplateHtml(html, template) {
// The inspector needs the values that produced each rendered template, not a
// fresh request's values. Contexts frequently contain helpers (such as
// `route`) or cyclic request objects, neither of which belongs in HTML. Keep
// the JSON-shaped portion and omit the rest rather than making a page fail to
// render just because it cannot be inspected.
function inspectableContext(data) {
const seen = new WeakSet();
try {
return JSON.parse(
JSON.stringify(data ?? {}, (_key, value) => {
if (typeof value === "function" || typeof value === "undefined") {
return undefined;
}
if (typeof value === "object" && value !== null) {
if (seen.has(value)) return undefined;
seen.add(value);
}
return value;
}),
);
} catch (_) {
return {};
}
}
function annotateTemplateHtml(html, template, data) {
const context = inspectableContext(data);
const contextId = randomUUID();
templateContextCache.set(contextId, { templateId: String(template.id), context });
function annotate($, element) {
const current = $(element).attr("data-bliss-templates");
let templates = [];
@ -109,10 +139,20 @@ function annotateTemplateHtml(html, template) {
templates.push({ id: String(template.id), name: template.name });
}
$(element).attr("data-bliss-templates", JSON.stringify(templates));
const rawContextIds = $(element).attr("data-bliss-template-context-ids");
let contextIds = {};
if (rawContextIds) {
try {
contextIds = JSON.parse(rawContextIds);
} catch (_) {}
}
contextIds[String(template.id)] = contextId;
$(element).attr("data-bliss-template-context-ids", JSON.stringify(contextIds));
if (!$(element).attr("data-bliss-template-id")) {
$(element).attr({
"data-bliss-template-id": String(template.id),
"data-bliss-template-name": template.name,
"data-bliss-template-context-id": contextId,
});
}
}
@ -143,7 +183,7 @@ function getTemplater(structId) {
const html = etaRender.call(this, templateName, data, meta);
if (typeof templateName !== "string") return html;
const template = getTemplateByName(structId, templateName);
return template ? annotateTemplateHtml(html, template) : html;
return template ? annotateTemplateHtml(html, template, data) : html;
};
templateCache.set(structId, etaInstance);
}
@ -302,6 +342,12 @@ function getTemplate(templateId) {
return db.prepare("SELECT * from templates where id = ?").get(templateId);
}
function getInspectableTemplateContext(contextId, templateId) {
const entry = templateContextCache.get(contextId);
if (!entry || entry.templateId !== String(templateId)) return null;
return entry.context;
}
function getTemplateContentByName(structId, name) {
return db
.prepare(
@ -736,6 +782,7 @@ module.exports = {
getTemplater,
getTemplates,
getTemplate,
getInspectableTemplateContext,
getTemplateContentByName,
getTemplateByName,
createTemplate,