@fiyuu/runtime 0.4.2 → 0.4.3
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/LICENSE +674 -0
- package/dist/bundler.d.ts +10 -0
- package/dist/bundler.d.ts.map +1 -0
- package/dist/bundler.js +125 -0
- package/dist/bundler.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +26 -0
- package/dist/cli.js.map +1 -0
- package/dist/client-runtime.d.ts +16 -0
- package/dist/client-runtime.d.ts.map +1 -0
- package/dist/client-runtime.js +528 -0
- package/dist/client-runtime.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/inspector.d.ts +39 -0
- package/dist/inspector.d.ts.map +1 -0
- package/dist/inspector.js +262 -0
- package/dist/inspector.js.map +1 -0
- package/dist/server-devtools.d.ts +14 -0
- package/dist/server-devtools.d.ts.map +1 -0
- package/dist/server-devtools.js +123 -0
- package/dist/server-devtools.js.map +1 -0
- package/dist/server-loader.d.ts +27 -0
- package/dist/server-loader.d.ts.map +1 -0
- package/dist/server-loader.js +159 -0
- package/dist/server-loader.js.map +1 -0
- package/dist/server-middleware.d.ts +8 -0
- package/dist/server-middleware.d.ts.map +1 -0
- package/dist/server-middleware.js +50 -0
- package/dist/server-middleware.js.map +1 -0
- package/dist/server-renderer.d.ts +42 -0
- package/dist/server-renderer.d.ts.map +1 -0
- package/dist/server-renderer.js +255 -0
- package/dist/server-renderer.js.map +1 -0
- package/dist/server-router.d.ts +15 -0
- package/dist/server-router.d.ts.map +1 -0
- package/dist/server-router.js +68 -0
- package/dist/server-router.js.map +1 -0
- package/dist/server-types.d.ts +168 -0
- package/dist/server-types.d.ts.map +1 -0
- package/dist/server-types.js +6 -0
- package/dist/server-types.js.map +1 -0
- package/dist/server-utils.d.ts +17 -0
- package/dist/server-utils.d.ts.map +1 -0
- package/dist/server-utils.js +103 -0
- package/dist/server-utils.js.map +1 -0
- package/dist/server-websocket.d.ts +8 -0
- package/dist/server-websocket.d.ts.map +1 -0
- package/dist/server-websocket.js +56 -0
- package/dist/server-websocket.js.map +1 -0
- package/dist/server.d.ts +69 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +846 -0
- package/dist/server.js.map +1 -0
- package/dist/service.d.ts +29 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +72 -0
- package/dist/service.js.map +1 -0
- package/package.json +11 -18
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { promises as fs, existsSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export async function buildInsightsReport(options) {
|
|
4
|
+
const items = await collectInsightItems(options);
|
|
5
|
+
const assistant = await buildAssistantOutput(options, items);
|
|
6
|
+
const summary = {
|
|
7
|
+
total: items.length,
|
|
8
|
+
high: items.filter((item) => item.severity === "high").length,
|
|
9
|
+
medium: items.filter((item) => item.severity === "medium").length,
|
|
10
|
+
low: items.filter((item) => item.severity === "low").length,
|
|
11
|
+
};
|
|
12
|
+
return {
|
|
13
|
+
generatedAt: new Date().toISOString(),
|
|
14
|
+
summary,
|
|
15
|
+
items,
|
|
16
|
+
assistant,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
async function collectInsightItems(options) {
|
|
20
|
+
const items = [];
|
|
21
|
+
for (const feature of options.features) {
|
|
22
|
+
items.push(...toFeatureStructureInsights(feature));
|
|
23
|
+
const fileEntries = Object.entries(feature.files).filter((entry) => Boolean(entry[1]));
|
|
24
|
+
for (const [, filePath] of fileEntries) {
|
|
25
|
+
const source = await readFileSafe(filePath);
|
|
26
|
+
if (!source) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (/dangerouslySetInnerHTML/.test(source)) {
|
|
30
|
+
items.push({
|
|
31
|
+
id: `security-dangerous-html-${filePath}`,
|
|
32
|
+
category: "security",
|
|
33
|
+
severity: "high",
|
|
34
|
+
title: "Potential XSS surface detected",
|
|
35
|
+
summary: "`dangerouslySetInnerHTML` is used in a route module.",
|
|
36
|
+
recommendation: "Prefer escaped rendering or sanitize content before passing HTML strings.",
|
|
37
|
+
route: feature.route,
|
|
38
|
+
file: filePath,
|
|
39
|
+
fixable: true,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (/\beval\s*\(|new\s+Function\s*\(/.test(source)) {
|
|
43
|
+
items.push({
|
|
44
|
+
id: `security-dynamic-eval-${filePath}`,
|
|
45
|
+
category: "security",
|
|
46
|
+
severity: "high",
|
|
47
|
+
title: "Dynamic code execution detected",
|
|
48
|
+
summary: "`eval` or `new Function` appears in application logic.",
|
|
49
|
+
recommendation: "Replace dynamic execution with explicit, typed control flow.",
|
|
50
|
+
route: feature.route,
|
|
51
|
+
file: filePath,
|
|
52
|
+
fixable: true,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const lineCount = source.split(/\r?\n/).length;
|
|
56
|
+
if (lineCount > 450) {
|
|
57
|
+
items.push({
|
|
58
|
+
id: `performance-large-module-${filePath}`,
|
|
59
|
+
category: "performance",
|
|
60
|
+
severity: "medium",
|
|
61
|
+
title: "Large route module",
|
|
62
|
+
summary: `Module has ${lineCount} lines and may increase parse and hydration cost.`,
|
|
63
|
+
recommendation: "Split heavy route logic into smaller server/client helpers.",
|
|
64
|
+
route: feature.route,
|
|
65
|
+
file: filePath,
|
|
66
|
+
fixable: true,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (feature.files["meta.ts"]) {
|
|
71
|
+
const metaSource = await readFileSafe(feature.files["meta.ts"]);
|
|
72
|
+
if (metaSource) {
|
|
73
|
+
const seoTitle = extractSeoField(metaSource, "title");
|
|
74
|
+
const seoDescription = extractSeoField(metaSource, "description");
|
|
75
|
+
if (!seoTitle) {
|
|
76
|
+
items.push({
|
|
77
|
+
id: `design-missing-seo-title-${feature.route}`,
|
|
78
|
+
category: "design",
|
|
79
|
+
severity: "low",
|
|
80
|
+
title: "SEO title missing in meta",
|
|
81
|
+
summary: "`meta.ts` exists but does not define `seo.title`.",
|
|
82
|
+
recommendation: "Add route-specific `seo.title` to improve previews and search snippets.",
|
|
83
|
+
route: feature.route,
|
|
84
|
+
file: feature.files["meta.ts"],
|
|
85
|
+
fixable: true,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
else if (seoTitle.length > 62) {
|
|
89
|
+
items.push({
|
|
90
|
+
id: `design-seo-title-length-${feature.route}`,
|
|
91
|
+
category: "design",
|
|
92
|
+
severity: "low",
|
|
93
|
+
title: "SEO title is longer than recommended",
|
|
94
|
+
summary: `Current title length is ${seoTitle.length} characters.`,
|
|
95
|
+
recommendation: "Keep seo titles around 45-60 characters to avoid truncation in SERP previews.",
|
|
96
|
+
route: feature.route,
|
|
97
|
+
file: feature.files["meta.ts"],
|
|
98
|
+
fixable: true,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
if (!seoDescription) {
|
|
102
|
+
items.push({
|
|
103
|
+
id: `design-missing-seo-description-${feature.route}`,
|
|
104
|
+
category: "design",
|
|
105
|
+
severity: "medium",
|
|
106
|
+
title: "SEO description missing in meta",
|
|
107
|
+
summary: "`meta.ts` does not define `seo.description`.",
|
|
108
|
+
recommendation: "Add a concise description (90-160 chars) for better search and social previews.",
|
|
109
|
+
route: feature.route,
|
|
110
|
+
file: feature.files["meta.ts"],
|
|
111
|
+
fixable: true,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
else if (seoDescription.length < 80 || seoDescription.length > 170) {
|
|
115
|
+
items.push({
|
|
116
|
+
id: `design-seo-description-length-${feature.route}`,
|
|
117
|
+
category: "design",
|
|
118
|
+
severity: "low",
|
|
119
|
+
title: "SEO description length can be improved",
|
|
120
|
+
summary: `Current description length is ${seoDescription.length} characters.`,
|
|
121
|
+
recommendation: "Keep seo descriptions in the 90-160 character range for reliable previews.",
|
|
122
|
+
route: feature.route,
|
|
123
|
+
file: feature.files["meta.ts"],
|
|
124
|
+
fixable: true,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
items.push(...(await collectGlobalSecurityInsights(options.appDirectory)));
|
|
131
|
+
items.push(...collectGlobalRenderInsights(options.features));
|
|
132
|
+
return dedupeInsights(items);
|
|
133
|
+
}
|
|
134
|
+
function toFeatureStructureInsights(feature) {
|
|
135
|
+
const items = [];
|
|
136
|
+
for (const missing of feature.missingRequiredFiles) {
|
|
137
|
+
items.push({
|
|
138
|
+
id: `architecture-missing-${feature.route}-${missing}`,
|
|
139
|
+
category: "architecture",
|
|
140
|
+
severity: missing === "schema.ts" ? "high" : "medium",
|
|
141
|
+
title: `Required file missing: ${missing}`,
|
|
142
|
+
summary: `Feature ${feature.route} is missing ${missing}.`,
|
|
143
|
+
recommendation: "Complete the feature contract so tooling and AI context stay deterministic.",
|
|
144
|
+
route: feature.route,
|
|
145
|
+
file: path.join(feature.directory, missing),
|
|
146
|
+
fixable: true,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return items;
|
|
150
|
+
}
|
|
151
|
+
async function collectGlobalSecurityInsights(appDirectory) {
|
|
152
|
+
const items = [];
|
|
153
|
+
const middlewarePath = path.join(appDirectory, "middleware.ts");
|
|
154
|
+
if (existsSync(middlewarePath)) {
|
|
155
|
+
const middlewareSource = await readFileSafe(middlewarePath);
|
|
156
|
+
if (middlewareSource && /access-control-allow-origin["']?\s*[:,=]\s*["']\*/i.test(middlewareSource)) {
|
|
157
|
+
items.push({
|
|
158
|
+
id: "security-open-cors-middleware",
|
|
159
|
+
category: "security",
|
|
160
|
+
severity: "high",
|
|
161
|
+
title: "Wildcard CORS header in middleware",
|
|
162
|
+
summary: "Middleware appears to allow all origins globally.",
|
|
163
|
+
recommendation: "Restrict allowed origins by environment and keep credentials disabled for public origins.",
|
|
164
|
+
file: middlewarePath,
|
|
165
|
+
fixable: true,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return items;
|
|
170
|
+
}
|
|
171
|
+
function collectGlobalRenderInsights(features) {
|
|
172
|
+
const csrCount = features.filter((feature) => feature.render === "csr").length;
|
|
173
|
+
if (features.length < 4) {
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
const ratio = csrCount / features.length;
|
|
177
|
+
if (ratio < 0.6) {
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
return [
|
|
181
|
+
{
|
|
182
|
+
id: "performance-csr-heavy",
|
|
183
|
+
category: "performance",
|
|
184
|
+
severity: ratio > 0.8 ? "high" : "medium",
|
|
185
|
+
title: "Project is CSR-heavy",
|
|
186
|
+
summary: `${csrCount}/${features.length} routes render in CSR mode.`,
|
|
187
|
+
recommendation: "Move content-driven pages to SSR where possible to reduce blank-first-paint risk.",
|
|
188
|
+
fixable: true,
|
|
189
|
+
},
|
|
190
|
+
];
|
|
191
|
+
}
|
|
192
|
+
async function buildAssistantOutput(options, items) {
|
|
193
|
+
void options;
|
|
194
|
+
return {
|
|
195
|
+
mode: "rule-only",
|
|
196
|
+
status: "ready",
|
|
197
|
+
details: "Using deterministic project checks (no integrated model).",
|
|
198
|
+
suggestions: createRuleBasedSuggestions(items),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function createRuleBasedSuggestions(items) {
|
|
202
|
+
if (items.length === 0) {
|
|
203
|
+
return [
|
|
204
|
+
"Run `fiyuu doctor --fix` after scaffolding new routes to keep contracts deterministic.",
|
|
205
|
+
"Keep SEO descriptions in 12-28 words and route-specific titles for stable search snippets.",
|
|
206
|
+
"Add focused tests around auth, middleware, and API routes before release builds.",
|
|
207
|
+
];
|
|
208
|
+
}
|
|
209
|
+
const topItems = items
|
|
210
|
+
.slice()
|
|
211
|
+
.sort((left, right) => severityScore(right.severity) - severityScore(left.severity))
|
|
212
|
+
.slice(0, 4);
|
|
213
|
+
return [
|
|
214
|
+
...topItems.map((item) => `${capitalize(item.category)}: ${item.recommendation}`),
|
|
215
|
+
"Use `fiyuu doctor --fix` for safe auto-fixes (SEO fields, missing execute(), fallback pages, className->class).",
|
|
216
|
+
].slice(0, 6);
|
|
217
|
+
}
|
|
218
|
+
function dedupeInsights(items) {
|
|
219
|
+
const seen = new Set();
|
|
220
|
+
const output = [];
|
|
221
|
+
for (const item of items) {
|
|
222
|
+
if (seen.has(item.id)) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
seen.add(item.id);
|
|
226
|
+
output.push(item);
|
|
227
|
+
}
|
|
228
|
+
return output;
|
|
229
|
+
}
|
|
230
|
+
async function readFileSafe(filePath) {
|
|
231
|
+
try {
|
|
232
|
+
return await fs.readFile(filePath, "utf8");
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function severityScore(severity) {
|
|
239
|
+
if (severity === "high") {
|
|
240
|
+
return 3;
|
|
241
|
+
}
|
|
242
|
+
if (severity === "medium") {
|
|
243
|
+
return 2;
|
|
244
|
+
}
|
|
245
|
+
return 1;
|
|
246
|
+
}
|
|
247
|
+
function capitalize(value) {
|
|
248
|
+
return `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
|
|
249
|
+
}
|
|
250
|
+
function extractSeoField(source, field) {
|
|
251
|
+
const seoBlock = source.match(/seo\s*:\s*\{([\s\S]*?)\}/m);
|
|
252
|
+
if (!seoBlock) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
const fieldRegex = new RegExp(`${field}\\s*:\\s*(["'\`])([\\s\\S]*?)\\1`, "m");
|
|
256
|
+
const match = seoBlock[1].match(fieldRegex);
|
|
257
|
+
if (!match) {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
return match[2].trim() || null;
|
|
261
|
+
}
|
|
262
|
+
//# sourceMappingURL=inspector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspector.js","sourceRoot":"","sources":["../../../../packages/runtime/src/inspector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev-only browser script generators for the Fiyuu runtime.
|
|
3
|
+
* These return inline <script> strings injected into the rendered document.
|
|
4
|
+
*/
|
|
5
|
+
import type { RenderMode } from "@fiyuu/core";
|
|
6
|
+
export declare function renderInsightsPanelScript(): string;
|
|
7
|
+
export declare function renderUnifiedToolsScript(input: {
|
|
8
|
+
route: string;
|
|
9
|
+
render: RenderMode;
|
|
10
|
+
renderTimeMs: number;
|
|
11
|
+
warnings: string[];
|
|
12
|
+
requestId: string;
|
|
13
|
+
}): string;
|
|
14
|
+
//# sourceMappingURL=server-devtools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-devtools.d.ts","sourceRoot":"","sources":["../../../../packages/runtime/src/server-devtools.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,wBAAgB,yBAAyB,IAAI,MAAM,CAqDlD;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,UAAU,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,MAAM,CAgET"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev-only browser script generators for the Fiyuu runtime.
|
|
3
|
+
* These return inline <script> strings injected into the rendered document.
|
|
4
|
+
*/
|
|
5
|
+
export function renderInsightsPanelScript() {
|
|
6
|
+
return `<script type="module">
|
|
7
|
+
const host=document.createElement('div');
|
|
8
|
+
host.style.cssText='position:fixed;left:16px;bottom:16px;z-index:9998;font:12px/1.5 ui-monospace,monospace';
|
|
9
|
+
const toggle=document.createElement('button');
|
|
10
|
+
toggle.textContent='Fiyuu Insights';
|
|
11
|
+
toggle.style.cssText='border:1px solid rgba(197,214,181,.18);background:rgba(18,24,19,.94);color:#f8f3ea;border-radius:999px;padding:10px 14px;box-shadow:0 18px 56px rgba(0,0,0,.22);cursor:pointer';
|
|
12
|
+
const panel=document.createElement('aside');
|
|
13
|
+
panel.style.cssText='display:none;margin-top:10px;width:min(420px,calc(100vw - 30px));max-height:min(72vh,620px);overflow:auto;background:rgba(18,24,19,.96);color:#f8f3ea;border:1px solid rgba(197,214,181,.16);border-radius:18px;padding:14px 16px;box-shadow:0 18px 56px rgba(0,0,0,.22);backdrop-filter:blur(14px)';
|
|
14
|
+
toggle.addEventListener('click',()=>{panel.style.display=panel.style.display==='none'?'block':'none';});
|
|
15
|
+
|
|
16
|
+
function renderItems(items){
|
|
17
|
+
if(!items.length){return '<li style="margin-top:6px">No findings.</li>'}
|
|
18
|
+
return items.slice(0,8).map((item)=>'<li style="margin-top:8px"><strong>['+item.severity.toUpperCase()+'] '+item.title+'</strong><br/><span style="opacity:.82">'+item.summary+'</span><br/><span style="opacity:.7">'+item.recommendation+'</span></li>').join('');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function mount(){
|
|
22
|
+
const response=await fetch('/__fiyuu/insights');
|
|
23
|
+
const data=await response.json();
|
|
24
|
+
const byCategory={
|
|
25
|
+
security:(data.items||[]).filter((item)=>item.category==='security'),
|
|
26
|
+
performance:(data.items||[]).filter((item)=>item.category==='performance'),
|
|
27
|
+
design:(data.items||[]).filter((item)=>item.category==='design'),
|
|
28
|
+
architecture:(data.items||[]).filter((item)=>item.category==='architecture'),
|
|
29
|
+
};
|
|
30
|
+
panel.innerHTML=''
|
|
31
|
+
+'<div style="display:flex;justify-content:space-between;gap:10px;align-items:center"><strong>AI Insights</strong><span style="opacity:.7">'+(data.generatedAt||'')+'</span></div>'
|
|
32
|
+
+'<p style="margin:8px 0 0;opacity:.82">'+data.summary.high+' high · '+data.summary.medium+' medium · '+data.summary.low+' low</p>'
|
|
33
|
+
+'<div style="margin-top:10px;display:flex;gap:8px"><button data-tab="findings" style="padding:6px 10px;border-radius:999px;border:1px solid rgba(197,214,181,.16);background:rgba(255,255,255,.06);color:#f8f3ea;cursor:pointer">Findings</button><button data-tab="assistant" style="padding:6px 10px;border-radius:999px;border:1px solid rgba(197,214,181,.16);background:transparent;color:#f8f3ea;cursor:pointer">Assistant</button></div>'
|
|
34
|
+
+'<section data-view="findings" style="margin-top:10px">'
|
|
35
|
+
+'<p style="margin:0"><strong>Security</strong></p><ul style="margin:6px 0 0;padding-left:18px">'+renderItems(byCategory.security)+'</ul>'
|
|
36
|
+
+'<p style="margin:10px 0 0"><strong>Performance</strong></p><ul style="margin:6px 0 0;padding-left:18px">'+renderItems(byCategory.performance)+'</ul>'
|
|
37
|
+
+'<p style="margin:10px 0 0"><strong>Design</strong></p><ul style="margin:6px 0 0;padding-left:18px">'+renderItems(byCategory.design)+'</ul>'
|
|
38
|
+
+'<p style="margin:10px 0 0"><strong>Architecture</strong></p><ul style="margin:6px 0 0;padding-left:18px">'+renderItems(byCategory.architecture)+'</ul>'
|
|
39
|
+
+'</section>'
|
|
40
|
+
+'<section data-view="assistant" style="display:none;margin-top:10px">'
|
|
41
|
+
+'<p style="margin:0;opacity:.84">Mode: '+data.assistant.mode+' · '+data.assistant.status+'</p>'
|
|
42
|
+
+'<p style="margin:8px 0 0;opacity:.72">'+data.assistant.details+'</p>'
|
|
43
|
+
+'<ul style="margin:8px 0 0;padding-left:18px">'+(data.assistant.suggestions||[]).map((line)=>'<li style="margin-top:6px">'+line+'</li>').join('')+'</ul>'
|
|
44
|
+
+'</section>';
|
|
45
|
+
|
|
46
|
+
const findingsButton=panel.querySelector('[data-tab="findings"]');
|
|
47
|
+
const assistantButton=panel.querySelector('[data-tab="assistant"]');
|
|
48
|
+
const findingsView=panel.querySelector('[data-view="findings"]');
|
|
49
|
+
const assistantView=panel.querySelector('[data-view="assistant"]');
|
|
50
|
+
findingsButton?.addEventListener('click',()=>{findingsView.style.display='block';assistantView.style.display='none';findingsButton.style.background='rgba(255,255,255,.06)';assistantButton.style.background='transparent';});
|
|
51
|
+
assistantButton?.addEventListener('click',()=>{findingsView.style.display='none';assistantView.style.display='block';assistantButton.style.background='rgba(255,255,255,.06)';findingsButton.style.background='transparent';});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
mount().catch((error)=>{console.warn('Fiyuu insights panel failed',error);});
|
|
55
|
+
host.append(toggle,panel);
|
|
56
|
+
document.body.append(host);
|
|
57
|
+
</script>`;
|
|
58
|
+
}
|
|
59
|
+
export function renderUnifiedToolsScript(input) {
|
|
60
|
+
const metrics = JSON.stringify({
|
|
61
|
+
route: input.route,
|
|
62
|
+
render: input.render,
|
|
63
|
+
renderTimeMs: input.renderTimeMs,
|
|
64
|
+
warnings: input.warnings,
|
|
65
|
+
requestId: input.requestId,
|
|
66
|
+
});
|
|
67
|
+
return `<script type="module">(function(){
|
|
68
|
+
const metrics=${metrics};
|
|
69
|
+
const removeLegacyPanels=()=>{const old=[...document.querySelectorAll('button')].filter((button)=>button.textContent==='Fiyuu Devtools'||button.textContent==='Fiyuu Insights');for(const button of old){const host=button.closest('div');if(host&&host.parentNode){host.parentNode.removeChild(host);}}};
|
|
70
|
+
removeLegacyPanels();
|
|
71
|
+
setInterval(removeLegacyPanels,500);
|
|
72
|
+
|
|
73
|
+
const host=document.createElement('div');
|
|
74
|
+
host.style.cssText='position:fixed;right:16px;bottom:16px;z-index:10001;font:12px/1.5 ui-monospace,monospace';
|
|
75
|
+
const toggle=document.createElement('button');
|
|
76
|
+
toggle.textContent='Fiyuu Console';
|
|
77
|
+
toggle.style.cssText='border:1px solid rgba(197,214,181,.18);background:rgba(18,24,19,.94);color:#f8f3ea;border-radius:999px;padding:10px 14px;box-shadow:0 18px 56px rgba(0,0,0,.22);cursor:pointer';
|
|
78
|
+
const panel=document.createElement('aside');
|
|
79
|
+
panel.style.cssText='display:none;margin-top:10px;width:min(500px,calc(100vw - 30px));max-height:min(78vh,700px);overflow:auto;background:rgba(18,24,19,.96);color:#f8f3ea;border:1px solid rgba(197,214,181,.16);border-radius:18px;padding:14px 16px;box-shadow:0 18px 56px rgba(0,0,0,.22);backdrop-filter:blur(14px)';
|
|
80
|
+
toggle.addEventListener('click',()=>{panel.style.display=panel.style.display==='none'?'block':'none';});
|
|
81
|
+
|
|
82
|
+
let serverTraceEnabled=false;
|
|
83
|
+
let pollingId;
|
|
84
|
+
const renderItems=(items)=>{if(!items.length){return '<li style="margin-top:6px">No findings.</li>';}return items.slice(0,8).map((item)=>'<li style="margin-top:8px"><strong>['+item.severity.toUpperCase()+'] '+item.title+'</strong><br/><span style="opacity:.82">'+item.summary+'</span><br/><span style="opacity:.72">'+item.recommendation+'</span></li>').join('');};
|
|
85
|
+
const renderServerItems=(events)=>{if(!events||!events.length){return '<li style="margin-top:6px">No server activity yet.</li>';}return events.slice(0,40).map((event)=>'<li style="margin-top:8px"><strong>'+event.event+'</strong><span style="opacity:.7"> ['+event.level.toUpperCase()+']</span><br/><span style="opacity:.75">'+event.at+'</span><br/><span style="opacity:.82">'+(event.details||'')+'</span></li>').join('');};
|
|
86
|
+
const refreshServerEvents=async()=>{if(!serverTraceEnabled){return;}const response=await fetch('/__fiyuu/server-events');if(!response.ok){return;}const payload=await response.json();const list=panel.querySelector('[data-server-list]');if(list){list.innerHTML=renderServerItems(payload.events||[]);}};
|
|
87
|
+
const startServerTrace=()=>{if(pollingId){clearInterval(pollingId);}pollingId=setInterval(()=>{refreshServerEvents().catch(()=>{});},1200);refreshServerEvents().catch(()=>{});};
|
|
88
|
+
const stopServerTrace=()=>{if(pollingId){clearInterval(pollingId);pollingId=undefined;}};
|
|
89
|
+
|
|
90
|
+
const mount=async()=>{
|
|
91
|
+
const [runtimeResponse,insightsResponse]=await Promise.all([fetch('/__fiyuu/devtools'),fetch('/__fiyuu/insights')]);
|
|
92
|
+
const runtime=runtimeResponse.ok?await runtimeResponse.json():{warnings:[],config:{featureFlags:{}}};
|
|
93
|
+
const insights=insightsResponse.ok?await insightsResponse.json():{summary:{high:0,medium:0,low:0},assistant:{mode:'rule-only',status:'fallback',details:'unavailable'},items:[]};
|
|
94
|
+
const warnings=(metrics.warnings.length?metrics.warnings:(runtime.warnings||[])).slice(0,4);
|
|
95
|
+
const flags=Object.entries((runtime.config&&runtime.config.featureFlags)||{}).map(([k,v])=>'<span style="display:inline-flex;margin:4px 6px 0 0;padding:3px 8px;border-radius:999px;background:rgba(233,240,224,.08)">'+k+': '+v+'</span>').join('')||'<span style="opacity:.7">none</span>';
|
|
96
|
+
const byCategory={security:(insights.items||[]).filter((i)=>i.category==='security'),performance:(insights.items||[]).filter((i)=>i.category==='performance'),design:(insights.items||[]).filter((i)=>i.category==='design'),architecture:(insights.items||[]).filter((i)=>i.category==='architecture')};
|
|
97
|
+
|
|
98
|
+
panel.innerHTML=''
|
|
99
|
+
+'<div style="display:flex;justify-content:space-between;gap:10px;align-items:center"><strong>Fiyuu Console</strong><span style="opacity:.7">'+metrics.requestId+'</span></div>'
|
|
100
|
+
+'<div style="margin-top:10px;display:flex;gap:8px"><button data-tab="runtime" style="padding:6px 10px;border-radius:999px;border:1px solid rgba(197,214,181,.16);background:rgba(255,255,255,.06);color:#f8f3ea;cursor:pointer">Runtime</button><button data-tab="insights" style="padding:6px 10px;border-radius:999px;border:1px solid rgba(197,214,181,.16);background:transparent;color:#f8f3ea;cursor:pointer">Insights</button><button data-tab="server" style="padding:6px 10px;border-radius:999px;border:1px solid rgba(197,214,181,.16);background:transparent;color:#f8f3ea;cursor:pointer">Server</button></div>'
|
|
101
|
+
+'<section data-view="runtime" style="margin-top:12px"><p style="margin:0;opacity:.86">Route <strong>'+metrics.route+'</strong> · '+String(metrics.render).toUpperCase()+' · '+metrics.renderTimeMs+'ms</p><p style="margin:8px 0 0;opacity:.7">Warnings</p><ul style="margin:6px 0 0;padding-left:18px">'+(warnings.map((w)=>'<li style="margin-top:6px">'+w+'</li>').join('')||'<li style="margin-top:6px">none</li>')+'</ul><p style="margin:10px 0 0;opacity:.7">Feature Flags</p><div style="margin-top:6px">'+flags+'</div></section>'
|
|
102
|
+
+'<section data-view="insights" style="display:none;margin-top:12px"><p style="margin:0;opacity:.86">'+insights.summary.high+' high · '+insights.summary.medium+' medium · '+insights.summary.low+' low</p><p style="margin:8px 0 0;opacity:.72">Assistant: '+insights.assistant.mode+' ('+insights.assistant.status+')</p><p style="margin:8px 0 0;opacity:.72">'+insights.assistant.details+'</p><div style="margin-top:10px"><strong>Security</strong><ul style="margin:6px 0 0;padding-left:18px">'+renderItems(byCategory.security)+'</ul></div><div style="margin-top:10px"><strong>Performance</strong><ul style="margin:6px 0 0;padding-left:18px">'+renderItems(byCategory.performance)+'</ul></div><div style="margin-top:10px"><strong>Design</strong><ul style="margin:6px 0 0;padding-left:18px">'+renderItems(byCategory.design)+'</ul></div><div style="margin-top:10px"><strong>Architecture</strong><ul style="margin:6px 0 0;padding-left:18px">'+renderItems(byCategory.architecture)+'</ul></div></section>'
|
|
103
|
+
+'<section data-view="server" style="display:none;margin-top:12px"><label style="display:flex;align-items:center;gap:8px"><input data-server-toggle type="checkbox"/> <span>Enable live server trace (dev only)</span></label><ul data-server-list style="margin:10px 0 0;padding-left:18px"></ul></section>';
|
|
104
|
+
|
|
105
|
+
const runtimeButton=panel.querySelector('[data-tab="runtime"]');
|
|
106
|
+
const insightsButton=panel.querySelector('[data-tab="insights"]');
|
|
107
|
+
const serverButton=panel.querySelector('[data-tab="server"]');
|
|
108
|
+
const runtimeView=panel.querySelector('[data-view="runtime"]');
|
|
109
|
+
const insightsView=panel.querySelector('[data-view="insights"]');
|
|
110
|
+
const serverView=panel.querySelector('[data-view="server"]');
|
|
111
|
+
const serverToggle=panel.querySelector('[data-server-toggle]');
|
|
112
|
+
runtimeButton?.addEventListener('click',()=>{runtimeView.style.display='block';insightsView.style.display='none';serverView.style.display='none';runtimeButton.style.background='rgba(255,255,255,.06)';insightsButton.style.background='transparent';serverButton.style.background='transparent';});
|
|
113
|
+
insightsButton?.addEventListener('click',()=>{runtimeView.style.display='none';insightsView.style.display='block';serverView.style.display='none';insightsButton.style.background='rgba(255,255,255,.06)';runtimeButton.style.background='transparent';serverButton.style.background='transparent';});
|
|
114
|
+
serverButton?.addEventListener('click',()=>{runtimeView.style.display='none';insightsView.style.display='none';serverView.style.display='block';serverButton.style.background='rgba(255,255,255,.06)';runtimeButton.style.background='transparent';insightsButton.style.background='transparent';});
|
|
115
|
+
serverToggle?.addEventListener('change',(event)=>{serverTraceEnabled=Boolean(event.target&&event.target.checked);if(serverTraceEnabled){startServerTrace();}else{stopServerTrace();}});
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
mount().catch((error)=>{console.warn('Fiyuu console mount failed',error);});
|
|
119
|
+
host.append(toggle,panel);
|
|
120
|
+
document.body.append(host);
|
|
121
|
+
})();</script>`;
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=server-devtools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-devtools.js","sourceRoot":"","sources":["../../../../packages/runtime/src/server-devtools.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,UAAU,yBAAyB;IACvC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAmDC,CAAC;AACX,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,KAMxC;IACC,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC;QAC7B,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,SAAS,EAAE,KAAK,CAAC,SAAS;KAC3B,CAAC,CAAC;IAEH,OAAO;gBACO,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAqDR,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module loading, layout stacking, meta merging, query caching,
|
|
3
|
+
* and GEA component rendering for the Fiyuu runtime server.
|
|
4
|
+
*/
|
|
5
|
+
import type { FeatureRecord, MetaDefinition } from "@fiyuu/core";
|
|
6
|
+
import type { RuntimeState } from "./server-types.js";
|
|
7
|
+
export declare function importModule(modulePath: string, mode: "dev" | "start"): Promise<unknown>;
|
|
8
|
+
export declare function resolveApiRouteModule(appDirectory: string, pathname: string): string | null;
|
|
9
|
+
export declare function loadMetaFile(filePath: string, mode: "dev" | "start"): Promise<MetaDefinition>;
|
|
10
|
+
export declare function loadLayoutMeta(directory: string, mode: "dev" | "start"): Promise<MetaDefinition>;
|
|
11
|
+
export declare function mergeMetaDefinitions(...definitions: MetaDefinition[]): MetaDefinition;
|
|
12
|
+
export declare function loadLayoutStack(appDirectory: string, feature: FeatureRecord, mode: "dev" | "start"): Promise<Array<{
|
|
13
|
+
component: unknown;
|
|
14
|
+
meta: MetaDefinition;
|
|
15
|
+
}>>;
|
|
16
|
+
export declare function loadFeatureMeta(feature: FeatureRecord, mode: "dev" | "start"): Promise<MetaDefinition>;
|
|
17
|
+
export declare function getCachedLayoutStack(state: RuntimeState, appDirectory: string, feature: FeatureRecord, mode: "dev" | "start"): Promise<Array<{
|
|
18
|
+
component: unknown;
|
|
19
|
+
meta: MetaDefinition;
|
|
20
|
+
}>>;
|
|
21
|
+
export declare function getCachedMergedMeta(state: RuntimeState, feature: FeatureRecord, layoutStack: Array<{
|
|
22
|
+
component: unknown;
|
|
23
|
+
meta: MetaDefinition;
|
|
24
|
+
}>, mode: "dev" | "start"): Promise<MetaDefinition>;
|
|
25
|
+
export declare function pruneQueryCache(state: RuntimeState, now: number): void;
|
|
26
|
+
export declare function renderGeaComponent(component: unknown, props: Record<string, unknown>): string;
|
|
27
|
+
//# sourceMappingURL=server-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-loader.d.ts","sourceRoot":"","sources":["../../../../packages/runtime/src/server-loader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,KAAK,EAA4C,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAKhG,wBAAsB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAyB9F;AAID,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAW3F;AAID,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,GAAG,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC,CAMnG;AAED,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,GAAG,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC,CAEtG;AAED,wBAAgB,oBAAoB,CAAC,GAAG,WAAW,EAAE,cAAc,EAAE,GAAG,cAAc,CAYrF;AAID,wBAAsB,eAAe,CACnC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,aAAa,EACtB,IAAI,EAAE,KAAK,GAAG,OAAO,GACpB,OAAO,CAAC,KAAK,CAAC;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,cAAc,CAAA;CAAE,CAAC,CAAC,CAmB9D;AAED,wBAAsB,eAAe,CAAC,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,KAAK,GAAG,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC,CAI5G;AAID,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,YAAY,EACnB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,aAAa,EACtB,IAAI,EAAE,KAAK,GAAG,OAAO,GACpB,OAAO,CAAC,KAAK,CAAC;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,cAAc,CAAA;CAAE,CAAC,CAAC,CAO9D;AAED,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,aAAa,EACtB,WAAW,EAAE,KAAK,CAAC;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,cAAc,CAAA;CAAE,CAAC,EAChE,IAAI,EAAE,KAAK,GAAG,OAAO,GACpB,OAAO,CAAC,cAAc,CAAC,CAazB;AAID,wBAAgB,eAAe,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAwBtE;AAMD,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CA2B7F"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module loading, layout stacking, meta merging, query caching,
|
|
3
|
+
* and GEA component rendering for the Fiyuu runtime server.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
8
|
+
import { QUERY_CACHE_MAX_ENTRIES, QUERY_CACHE_SWEEP_INTERVAL_MS } from "./server-router.js";
|
|
9
|
+
// ── Dynamic module import ─────────────────────────────────────────────────────
|
|
10
|
+
export async function importModule(modulePath, mode) {
|
|
11
|
+
const fileUrl = pathToFileURL(modulePath).href;
|
|
12
|
+
try {
|
|
13
|
+
return await import(mode === "dev" ? `${fileUrl}?t=${Date.now()}` : fileUrl);
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
17
|
+
const shortPath = modulePath.replace(process.cwd(), ".");
|
|
18
|
+
let hint = "";
|
|
19
|
+
if (message.includes("Cannot find package") || message.includes("ERR_MODULE_NOT_FOUND")) {
|
|
20
|
+
const match = message.match(/Cannot find (?:package|module) '([^']+)'/);
|
|
21
|
+
const missing = match ? match[1] : "a dependency";
|
|
22
|
+
hint = `\n → Missing package: "${missing}". Run \`npm install\` in the project root.`;
|
|
23
|
+
}
|
|
24
|
+
else if (message.includes("SyntaxError") || message.includes("Unexpected token")) {
|
|
25
|
+
hint = `\n → Syntax error in ${shortPath}. Check for typos or invalid TypeScript.`;
|
|
26
|
+
}
|
|
27
|
+
else if (message.includes("ERR_INVALID_URL")) {
|
|
28
|
+
hint = `\n → Invalid file path: ${shortPath}`;
|
|
29
|
+
}
|
|
30
|
+
const enhanced = new Error(`Failed to load module: ${shortPath}\n ${message}${hint}`);
|
|
31
|
+
enhanced.stack = err instanceof Error ? err.stack : undefined;
|
|
32
|
+
throw enhanced;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// ── API route resolution ──────────────────────────────────────────────────────
|
|
36
|
+
export function resolveApiRouteModule(appDirectory, pathname) {
|
|
37
|
+
const relativePath = pathname.replace(/^\//, "");
|
|
38
|
+
const normalizedRoot = path.resolve(appDirectory) + path.sep;
|
|
39
|
+
const directModule = path.resolve(appDirectory, relativePath, "route.ts");
|
|
40
|
+
if (!directModule.startsWith(normalizedRoot))
|
|
41
|
+
return null; // path traversal guard
|
|
42
|
+
if (existsSync(directModule))
|
|
43
|
+
return directModule;
|
|
44
|
+
const rootModule = path.resolve(appDirectory, relativePath + ".ts");
|
|
45
|
+
if (!rootModule.startsWith(normalizedRoot))
|
|
46
|
+
return null; // path traversal guard
|
|
47
|
+
return existsSync(rootModule) ? rootModule : null;
|
|
48
|
+
}
|
|
49
|
+
// ── Meta loading & merging ────────────────────────────────────────────────────
|
|
50
|
+
export async function loadMetaFile(filePath, mode) {
|
|
51
|
+
if (!existsSync(filePath)) {
|
|
52
|
+
return { intent: "" };
|
|
53
|
+
}
|
|
54
|
+
const module = (await importModule(filePath, mode));
|
|
55
|
+
return module.default ?? { intent: "" };
|
|
56
|
+
}
|
|
57
|
+
export async function loadLayoutMeta(directory, mode) {
|
|
58
|
+
return loadMetaFile(path.join(directory, "layout.meta.ts"), mode);
|
|
59
|
+
}
|
|
60
|
+
export function mergeMetaDefinitions(...definitions) {
|
|
61
|
+
return definitions.reduce((current, item) => ({
|
|
62
|
+
...current,
|
|
63
|
+
...item,
|
|
64
|
+
seo: {
|
|
65
|
+
...current.seo,
|
|
66
|
+
...item.seo,
|
|
67
|
+
},
|
|
68
|
+
}), { intent: "" });
|
|
69
|
+
}
|
|
70
|
+
// ── Layout stack loading ──────────────────────────────────────────────────────
|
|
71
|
+
export async function loadLayoutStack(appDirectory, feature, mode) {
|
|
72
|
+
const parts = feature.feature ? feature.feature.split("/") : [];
|
|
73
|
+
const directories = [appDirectory];
|
|
74
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
75
|
+
directories.push(path.join(appDirectory, ...parts.slice(0, index + 1)));
|
|
76
|
+
}
|
|
77
|
+
const stack = [];
|
|
78
|
+
for (const directory of directories) {
|
|
79
|
+
const layoutFile = path.join(directory, "layout.tsx");
|
|
80
|
+
const metaFile = path.join(directory, "layout.meta.ts");
|
|
81
|
+
if (existsSync(layoutFile)) {
|
|
82
|
+
const module = (await importModule(layoutFile, mode));
|
|
83
|
+
if (module.default) {
|
|
84
|
+
stack.push({ component: module.default, meta: await loadMetaFile(metaFile, mode) });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return stack;
|
|
89
|
+
}
|
|
90
|
+
export async function loadFeatureMeta(feature, mode) {
|
|
91
|
+
return feature.files["meta.ts"]
|
|
92
|
+
? loadMetaFile(feature.files["meta.ts"], mode)
|
|
93
|
+
: { intent: feature.intent ?? "" };
|
|
94
|
+
}
|
|
95
|
+
// ── Cached layout stack (production only) ────────────────────────────────────
|
|
96
|
+
export async function getCachedLayoutStack(state, appDirectory, feature, mode) {
|
|
97
|
+
const cached = state.layoutStackCache.get(feature.route);
|
|
98
|
+
if (cached)
|
|
99
|
+
return cached;
|
|
100
|
+
const layoutStack = await loadLayoutStack(appDirectory, feature, mode);
|
|
101
|
+
state.layoutStackCache.set(feature.route, layoutStack);
|
|
102
|
+
return layoutStack;
|
|
103
|
+
}
|
|
104
|
+
export async function getCachedMergedMeta(state, feature, layoutStack, mode) {
|
|
105
|
+
const cached = state.mergedMetaCache.get(feature.route);
|
|
106
|
+
if (cached)
|
|
107
|
+
return cached;
|
|
108
|
+
let featureMeta = state.featureMetaCache.get(feature.route);
|
|
109
|
+
if (!featureMeta) {
|
|
110
|
+
featureMeta = await loadFeatureMeta(feature, mode);
|
|
111
|
+
state.featureMetaCache.set(feature.route, featureMeta);
|
|
112
|
+
}
|
|
113
|
+
const merged = mergeMetaDefinitions(...layoutStack.map((item) => item.meta), featureMeta);
|
|
114
|
+
state.mergedMetaCache.set(feature.route, merged);
|
|
115
|
+
return merged;
|
|
116
|
+
}
|
|
117
|
+
// ── Query cache ───────────────────────────────────────────────────────────────
|
|
118
|
+
export function pruneQueryCache(state, now) {
|
|
119
|
+
if (now - state.queryCacheLastPruneAt < QUERY_CACHE_SWEEP_INTERVAL_MS &&
|
|
120
|
+
state.queryCache.size < QUERY_CACHE_MAX_ENTRIES) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
state.queryCacheLastPruneAt = now;
|
|
124
|
+
for (const [key, entry] of state.queryCache) {
|
|
125
|
+
if (entry.expiresAt <= now) {
|
|
126
|
+
state.queryCache.delete(key);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (state.queryCache.size <= QUERY_CACHE_MAX_ENTRIES)
|
|
130
|
+
return;
|
|
131
|
+
const survivors = [...state.queryCache.entries()].sort((left, right) => left[1].expiresAt - right[1].expiresAt);
|
|
132
|
+
const overflowCount = survivors.length - QUERY_CACHE_MAX_ENTRIES;
|
|
133
|
+
for (let index = 0; index < overflowCount; index += 1) {
|
|
134
|
+
state.queryCache.delete(survivors[index][0]);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// ── GEA component rendering ───────────────────────────────────────────────────
|
|
138
|
+
const geaComponentModeCache = new WeakMap();
|
|
139
|
+
export function renderGeaComponent(component, props) {
|
|
140
|
+
if (typeof component !== "function") {
|
|
141
|
+
throw new Error("Route module default export must be a Gea component class or function.");
|
|
142
|
+
}
|
|
143
|
+
const candidate = component;
|
|
144
|
+
const componentKey = candidate;
|
|
145
|
+
const cachedMode = geaComponentModeCache.get(componentKey);
|
|
146
|
+
const mode = cachedMode ?? (typeof candidate.prototype?.template === "function" ? "class" : "function");
|
|
147
|
+
if (!cachedMode) {
|
|
148
|
+
geaComponentModeCache.set(componentKey, mode);
|
|
149
|
+
}
|
|
150
|
+
if (mode === "class") {
|
|
151
|
+
const instance = new candidate(props);
|
|
152
|
+
if (typeof instance.template === "function") {
|
|
153
|
+
return String(instance.template(instance.props ?? props));
|
|
154
|
+
}
|
|
155
|
+
return String(instance.toString());
|
|
156
|
+
}
|
|
157
|
+
return String(candidate(props));
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=server-loader.js.map
|