@jay-framework/production-server 0.25.4 → 0.26.0

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.
@@ -1,761 +0,0 @@
1
- var __defProp = Object.defineProperty;
2
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
- var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- import fs from "node:fs/promises";
5
- import path from "node:path";
6
- import { resolveErrorPageToken, buildErrorViewState, headMetaToHeadTags, mergeHeadTags, serializeHeadTags, serializeScripts, getClientInitData, makePassthroughInstanceComponent, renderFastChangingData, actionRegistry, setClientInitData } from "@jay-framework/stack-server-runtime";
7
- import { deepMergeViewStates } from "@jay-framework/view-state-merge";
8
- import { asyncSwapScript } from "@jay-framework/ssr-runtime";
9
- import { getLogger } from "@jay-framework/logger";
10
- import { isJayAction, isJayStreamAction } from "@jay-framework/fullstack-component";
11
- class FilesystemArtifactStore {
12
- constructor(basePath) {
13
- __publicField(this, "manifestCache");
14
- __publicField(this, "metadataMtime");
15
- __publicField(this, "moduleCache", /* @__PURE__ */ new Map());
16
- this.basePath = basePath;
17
- }
18
- async readManifest() {
19
- const metadataPath = path.join(this.basePath, "build-metadata.json");
20
- const manifestPath = path.join(this.basePath, "route-manifest.json");
21
- try {
22
- const metaStat = await fs.stat(metadataPath);
23
- if (this.manifestCache && this.metadataMtime === metaStat.mtimeMs) {
24
- return this.manifestCache.manifest;
25
- }
26
- this.metadataMtime = metaStat.mtimeMs;
27
- } catch {
28
- }
29
- const manifest = JSON.parse(await fs.readFile(manifestPath, "utf-8"));
30
- const manifestStat = await fs.stat(manifestPath);
31
- this.manifestCache = { manifest, mtime: manifestStat.mtimeMs };
32
- return manifest;
33
- }
34
- async readCacheData(relativePath) {
35
- const fullPath = path.join(this.basePath, relativePath);
36
- const cacheData = JSON.parse(await fs.readFile(fullPath, "utf-8"));
37
- return {
38
- slowViewState: cacheData.slowViewState || {},
39
- carryForward: cacheData.carryForward || {}
40
- };
41
- }
42
- async readPagePartsConfig(relativePath) {
43
- return JSON.parse(await fs.readFile(path.join(this.basePath, relativePath), "utf-8"));
44
- }
45
- async loadServerElement(relativePath) {
46
- return this.loadModule(relativePath);
47
- }
48
- getAssetPath(relativePath) {
49
- return path.join(this.basePath, relativePath);
50
- }
51
- getBuildDir() {
52
- return this.basePath;
53
- }
54
- async loadModule(modulePath, local) {
55
- if (local !== false) {
56
- const fullPath = path.isAbsolute(modulePath) ? modulePath : path.join(this.basePath, modulePath);
57
- try {
58
- const stat = await fs.stat(fullPath);
59
- const cached = this.moduleCache.get(modulePath);
60
- if (cached && stat.mtimeMs === cached.mtime) {
61
- return cached.module;
62
- }
63
- const mod = await import(fullPath + "?t=" + stat.mtimeMs);
64
- this.moduleCache.set(modulePath, { module: mod, mtime: stat.mtimeMs });
65
- return mod;
66
- } catch {
67
- if (local) throw new Error(`Local module not found: ${fullPath}`);
68
- }
69
- }
70
- return import(modulePath);
71
- }
72
- }
73
- function matchRequest(manifest, pathname) {
74
- const urlSegments = pathname.split("/").filter((s) => s.length > 0);
75
- for (const route of manifest.routes) {
76
- const params = matchSegments(route.segments, urlSegments);
77
- if (params) {
78
- const instance = findInstance(route, params);
79
- if (instance) {
80
- return { route, instance, params, pathname };
81
- }
82
- }
83
- }
84
- return void 0;
85
- }
86
- function matchSegments(routeSegments, urlSegments) {
87
- const params = {};
88
- let urlIdx = 0;
89
- for (let i = 0; i < routeSegments.length; i++) {
90
- const seg = routeSegments[i];
91
- if (seg.type === "static") {
92
- if (urlIdx >= urlSegments.length || urlSegments[urlIdx] !== seg.value) {
93
- return void 0;
94
- }
95
- urlIdx++;
96
- } else if (seg.type === "param") {
97
- if (urlIdx >= urlSegments.length) return void 0;
98
- params[seg.value] = urlSegments[urlIdx];
99
- urlIdx++;
100
- } else if (seg.type === "optional") {
101
- if (urlIdx < urlSegments.length) {
102
- params[seg.value] = urlSegments[urlIdx];
103
- urlIdx++;
104
- }
105
- } else if (seg.type === "catchAll") {
106
- if (urlIdx >= urlSegments.length) return void 0;
107
- params[seg.value] = urlSegments.slice(urlIdx).join("/");
108
- urlIdx = urlSegments.length;
109
- } else if (seg.type === "optionalCatchAll") {
110
- if (urlIdx < urlSegments.length) {
111
- params[seg.value] = urlSegments.slice(urlIdx).join("/");
112
- urlIdx = urlSegments.length;
113
- }
114
- }
115
- }
116
- if (urlIdx !== urlSegments.length) return void 0;
117
- return params;
118
- }
119
- function findInstance(route, params) {
120
- const paramNames = new Set(
121
- route.segments.filter((s) => s.type !== "static").map((s) => s.value)
122
- );
123
- return route.instances.find((instance) => {
124
- for (const name of paramNames) {
125
- const urlVal = params[name];
126
- const instVal = instance.params[name];
127
- if (urlVal === void 0 && instVal === void 0) continue;
128
- if (urlVal !== instVal) return false;
129
- }
130
- return true;
131
- });
132
- }
133
- function buildImportMap(sharedManifest, publicBasePath, sharedDir = "shared") {
134
- const imports = {};
135
- for (const [pkgName, hashedFile] of Object.entries(sharedManifest)) {
136
- imports[pkgName] = `${publicBasePath}${sharedDir}/${hashedFile}`;
137
- }
138
- return imports;
139
- }
140
- async function renderErrorPage(manifest, artifacts, staticBaseUrl, info) {
141
- const plainText = () => new Response(info.message || info.title || "Error", {
142
- status: info.status,
143
- headers: { "Content-Type": "text/plain; charset=utf-8" }
144
- });
145
- const errorPages = manifest.errorPages;
146
- if (!errorPages) return plainText();
147
- const token = resolveErrorPageToken(Object.keys(errorPages), info.status);
148
- if (!token) return plainText();
149
- const entry = errorPages[token];
150
- try {
151
- return await streamErrorPage(entry, manifest, artifacts, staticBaseUrl, info);
152
- } catch (err) {
153
- getLogger().error(
154
- `[Server] Error page "${token}" render failed (${info.status} ${info.url}): ${err?.message}`
155
- );
156
- return plainText();
157
- }
158
- }
159
- async function streamErrorPage(entry, manifest, artifacts, staticBaseUrl, info) {
160
- const slowViewState = entry.slowCache?.slowViewState ?? {};
161
- const fullViewState = buildErrorViewState(info, slowViewState);
162
- const headTagSources = [];
163
- if (manifest.siteHeadTags && manifest.siteHeadTags.length > 0)
164
- headTagSources.push(manifest.siteHeadTags);
165
- const templateHeadTags = headMetaToHeadTags(entry.headMeta, fullViewState);
166
- const headTags = headTagSources.length > 0 || templateHeadTags.length > 0 ? mergeHeadTags(headTagSources, templateHeadTags) : [];
167
- const hasCustomTitle = headTags.some((t) => t.tag?.toLowerCase() === "title");
168
- const titleTag = hasCustomTitle ? "" : ` <title>${info.status} Error</title>
169
- `;
170
- const preconnectTags = headTags.filter(
171
- (t) => t.tag === "link" && t.attrs?.rel === "preconnect"
172
- );
173
- const otherHeadTags = headTags.filter(
174
- (t) => !(t.tag === "link" && t.attrs?.rel === "preconnect")
175
- );
176
- const preconnectHtml = preconnectTags.length > 0 ? serializeHeadTags(preconnectTags) + "\n" : "";
177
- const headTagsHtml = otherHeadTags.length > 0 ? serializeHeadTags(otherHeadTags) + "\n" : "";
178
- const headScriptsHtml = serializeScripts(entry.scripts, "head");
179
- const bodyScriptsHtml = serializeScripts(entry.scripts, "body");
180
- const serverElement = await artifacts.loadServerElement(entry.serverElementPath);
181
- const asyncPromises = [];
182
- const importMap = buildImportMap(manifest.sharedManifest, staticBaseUrl);
183
- if (entry.routeHydratePath) {
184
- importMap["jay-route-hydrate"] = `${staticBaseUrl}${entry.routeHydratePath}`;
185
- }
186
- const clientBundleUrl = entry.routeClientBundlePath ? `${staticBaseUrl}${entry.routeClientBundlePath}` : "";
187
- const sharedPreloads = entry.sharedDeps ? entry.sharedDeps.map((dep) => importMap[dep]).filter(Boolean) : Object.values(importMap);
188
- const preloadUrls = clientBundleUrl ? [...sharedPreloads, clientBundleUrl] : [];
189
- const modulePreloads = preloadUrls.map((url) => ` <link rel="modulepreload" href="${url}" />`).join("\n");
190
- const cssUrl = entry.routeCssPath ? `${staticBaseUrl}${entry.routeCssPath}` : "";
191
- const cssPreload = cssUrl ? ` <link rel="preload" href="${cssUrl}" as="style" />` : "";
192
- const cssLink = cssUrl ? ` <link rel="stylesheet" href="${cssUrl}" />` : "";
193
- const cssImportPreloads = (entry.cssImports ?? []).map((url) => ` <link rel="preload" href="${url}" as="style" />`).join("\n");
194
- const encoder = new TextEncoder();
195
- const stream = new ReadableStream({
196
- async start(controller) {
197
- const write = (s) => controller.enqueue(encoder.encode(s));
198
- const headParts = [headTagsHtml, modulePreloads, cssLink, headScriptsHtml].filter(Boolean).join("\n");
199
- write(`<!doctype html>
200
- <html lang="en">
201
- <head>
202
- <meta charset="UTF-8" />
203
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
204
- ${titleTag}${[preconnectHtml, cssPreload, cssImportPreloads].filter(Boolean).map((l) => l + "\n").join(
205
- ""
206
- )} <script type="importmap">${JSON.stringify({ imports: importMap })}<\/script>
207
- ${headParts}
208
- </head>
209
- <body>
210
- <div id="target">`);
211
- serverElement.renderToStream(fullViewState, {
212
- write: (chunk) => write(chunk),
213
- onAsync: (promise, id, templates) => {
214
- asyncPromises.push(
215
- promise.then(
216
- (val) => asyncSwapScript(id, templates.resolved(val)),
217
- (err) => asyncSwapScript(id, templates.rejected(err))
218
- )
219
- );
220
- }
221
- });
222
- write("</div>");
223
- const asyncScripts = (await Promise.all(asyncPromises)).filter((s) => s).join("");
224
- if (asyncScripts) write(asyncScripts);
225
- if (clientBundleUrl) {
226
- const clientInitData = getClientInitData();
227
- const initArgs = `${JSON.stringify({})}, ${JSON.stringify(fullViewState)}, ${JSON.stringify({})}, ${JSON.stringify(clientInitData)}`;
228
- write(`
229
- <script type="module">
230
- import { init } from '${clientBundleUrl}';
231
- await init(${initArgs});
232
- <\/script>`);
233
- }
234
- if (bodyScriptsHtml) write("\n " + bodyScriptsHtml);
235
- write(`
236
- </body>
237
- </html>`);
238
- controller.close();
239
- }
240
- });
241
- return new Response(stream, {
242
- status: info.status,
243
- headers: { "Content-Type": "text/html; charset=utf-8" }
244
- });
245
- }
246
- async function loadPagePartsFromConfig(configPath, artifacts) {
247
- const config = await artifacts.readPagePartsConfig(configPath);
248
- async function importModule(entry) {
249
- if (!entry.modulePath) {
250
- throw new Error(
251
- `Empty modulePath in page-parts.json for "${entry.exportName}" (source: ${entry.source}). Rebuild required.`
252
- );
253
- }
254
- return artifacts.loadModule(entry.modulePath, entry.source === "local");
255
- }
256
- const parts = [];
257
- for (const entry of config.parts) {
258
- const mod = await importModule(entry);
259
- parts.push({
260
- compDefinition: mod[entry.exportName] ?? mod.default,
261
- key: entry.key,
262
- clientImport: "",
263
- clientPart: "",
264
- contractInfo: entry.contractInfo,
265
- headlessProps: entry.headlessProps
266
- });
267
- }
268
- const headlessInstanceComponents = [];
269
- for (const entry of config.instanceComponents) {
270
- const serveTimeContract = {
271
- props: entry.propNames.map((name) => ({ name }))
272
- };
273
- if (entry.structural) {
274
- headlessInstanceComponents.push({
275
- contractName: entry.contractName,
276
- compDefinition: makePassthroughInstanceComponent(entry.structuralTags ?? []),
277
- contract: serveTimeContract
278
- });
279
- continue;
280
- }
281
- const mod = await importModule(entry);
282
- headlessInstanceComponents.push({
283
- contractName: entry.contractName,
284
- compDefinition: mod[entry.exportName] ?? mod.default,
285
- contract: serveTimeContract
286
- });
287
- }
288
- return {
289
- parts,
290
- headlessContracts: [],
291
- headlessInstanceComponents,
292
- discoveredInstances: [],
293
- forEachInstances: config.forEachInstances,
294
- keyedPartModules: [],
295
- headlessModuleInfos: []
296
- };
297
- }
298
- const pagePartsCache = /* @__PURE__ */ new Map();
299
- async function getPageParts(route, artifacts, cachePath) {
300
- const cacheKey = route.pattern;
301
- const cached = pagePartsCache.get(cacheKey);
302
- if (cached) return cached;
303
- const routeDir = path.dirname(cachePath);
304
- const configRelPath = path.join(routeDir, "page-parts.json");
305
- const parts = await loadPagePartsFromConfig(configRelPath, artifacts);
306
- pagePartsCache.set(cacheKey, parts);
307
- return parts;
308
- }
309
- async function fetchPageRequest(match, manifest, requestUrl, artifacts, staticBaseUrl, cookies = {}) {
310
- const { route, instance } = match;
311
- const t0 = Date.now();
312
- let tCache = 0, tParts = 0;
313
- const [cached, pageParts] = await Promise.all([
314
- artifacts.readCacheData(instance.cachePath).then((r) => {
315
- tCache = Date.now() - t0;
316
- return r;
317
- }),
318
- getPageParts(route, artifacts, instance.cachePath).then((r) => {
319
- tParts = Date.now() - t0;
320
- return r;
321
- })
322
- ]);
323
- const tData = Date.now();
324
- const query = Object.fromEntries(requestUrl.searchParams.entries());
325
- const cf = cached.carryForward;
326
- const fastResult = await renderFastChangingData(
327
- match.params,
328
- { params: match.params, query },
329
- cached.carryForward,
330
- pageParts.parts,
331
- cf.__instances,
332
- pageParts.forEachInstances,
333
- pageParts.headlessInstanceComponents,
334
- cached.slowViewState,
335
- query,
336
- cookies
337
- );
338
- const tFast = Date.now();
339
- if (fastResult.kind === "Redirect") {
340
- return new Response(null, {
341
- status: fastResult.status,
342
- headers: { Location: fastResult.location }
343
- });
344
- }
345
- if (fastResult.kind === "ServerError" || fastResult.kind === "ClientError") {
346
- return renderErrorPage(manifest, artifacts, staticBaseUrl, {
347
- status: fastResult.status,
348
- url: requestUrl.pathname,
349
- title: fastResult.title,
350
- message: fastResult.message,
351
- code: fastResult.code,
352
- details: fastResult.details
353
- });
354
- }
355
- const fastViewState = fastResult.rendered || {};
356
- const fastCarryForward = fastResult.carryForward || {};
357
- const instanceSlowVS = cf.__instances?.slowViewStates;
358
- const fullSlowViewState = instanceSlowVS && Object.keys(instanceSlowVS).length > 0 ? { ...cached.slowViewState, __headlessInstances: instanceSlowVS } : cached.slowViewState;
359
- const fullViewState = deepMergeViewStates(
360
- fullSlowViewState,
361
- fastViewState,
362
- route.trackByMap || {}
363
- );
364
- fullViewState.__jay = {
365
- params: match.params,
366
- url: { path: requestUrl.pathname }
367
- };
368
- const headTagSources = [];
369
- if (manifest.siteHeadTags && manifest.siteHeadTags.length > 0)
370
- headTagSources.push(manifest.siteHeadTags);
371
- const slowHeadTags = cf.__slowHeadTags;
372
- if (slowHeadTags) headTagSources.push(...slowHeadTags);
373
- if (fastResult.headTags) headTagSources.push(fastResult.headTags);
374
- const templateHeadTags = headMetaToHeadTags(route.headMeta, fullViewState);
375
- const headTags = headTagSources.length > 0 || templateHeadTags.length > 0 ? mergeHeadTags(headTagSources, templateHeadTags) : [];
376
- const hasCustomTitle = headTags.some((t) => t.tag?.toLowerCase() === "title");
377
- const titleTag = hasCustomTitle ? "" : " <title>Vite + TS</title>\n";
378
- const preconnectTags = headTags.filter(
379
- (t) => t.tag === "link" && t.attrs?.rel === "preconnect"
380
- );
381
- const otherHeadTags = headTags.filter(
382
- (t) => !(t.tag === "link" && t.attrs?.rel === "preconnect")
383
- );
384
- const preconnectHtml = preconnectTags.length > 0 ? serializeHeadTags(preconnectTags) + "\n" : "";
385
- const headTagsHtml = otherHeadTags.length > 0 ? serializeHeadTags(otherHeadTags) + "\n" : "";
386
- const headScriptsHtml = serializeScripts(route.scripts, "head");
387
- const bodyScriptsHtml = serializeScripts(route.scripts, "body");
388
- const serverElementPath = route.serverElementPath || instance.serverElementPath;
389
- const tLoadStart = Date.now();
390
- const serverElement = await artifacts.loadServerElement(serverElementPath);
391
- const tLoad = Date.now();
392
- const asyncPromises = [];
393
- const importMap = buildImportMap(manifest.sharedManifest, staticBaseUrl);
394
- if (route.routeHydratePath) {
395
- importMap["jay-route-hydrate"] = `${staticBaseUrl}${route.routeHydratePath}`;
396
- }
397
- const clientBundleUrl = route.routeClientBundlePath ? `${staticBaseUrl}${route.routeClientBundlePath}` : `${staticBaseUrl}${instance.clientBundlePath}`;
398
- const sharedPreloads = route.sharedDeps ? route.sharedDeps.map((dep) => importMap[dep]).filter(Boolean) : Object.values(importMap);
399
- const preloadUrls = [...sharedPreloads, clientBundleUrl];
400
- const modulePreloads = preloadUrls.map((url) => ` <link rel="modulepreload" href="${url}" />`).join("\n");
401
- const cssUrl = instance.clientCssPath ? `${staticBaseUrl}${instance.clientCssPath}` : "";
402
- const cssPreload = cssUrl ? ` <link rel="preload" href="${cssUrl}" as="style" />` : "";
403
- const cssLink = cssUrl ? ` <link rel="stylesheet" href="${cssUrl}" />` : "";
404
- const cssImportPreloads = (route.cssImports ?? []).map((url) => ` <link rel="preload" href="${url}" as="style" />`).join("\n");
405
- const encoder = new TextEncoder();
406
- const stream = new ReadableStream({
407
- async start(controller) {
408
- const write = (s) => controller.enqueue(encoder.encode(s));
409
- const headParts = [headTagsHtml, modulePreloads, cssLink, headScriptsHtml].filter(Boolean).join("\n");
410
- write(`<!doctype html>
411
- <html lang="en">
412
- <head>
413
- <meta charset="UTF-8" />
414
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
415
- ${titleTag}${[preconnectHtml, cssPreload, cssImportPreloads].filter(Boolean).map((l) => l + "\n").join(
416
- ""
417
- )} <script type="importmap">${JSON.stringify({ imports: importMap })}<\/script>
418
- ${headParts}
419
- </head>
420
- <body>
421
- <div id="target">`);
422
- const tSsrStart = Date.now();
423
- serverElement.renderToStream(fullViewState, {
424
- write: (chunk) => write(chunk),
425
- onAsync: (promise, id, templates) => {
426
- asyncPromises.push(
427
- promise.then(
428
- (val) => asyncSwapScript(id, templates.resolved(val)),
429
- (err) => asyncSwapScript(id, templates.rejected(err))
430
- )
431
- );
432
- }
433
- });
434
- const tSsr = Date.now();
435
- write("</div>");
436
- const asyncScripts = (await Promise.all(asyncPromises)).filter((s) => s).join("");
437
- if (asyncScripts) write(asyncScripts);
438
- const clientInitData = getClientInitData();
439
- const initArgs = route.routeClientBundlePath ? `${JSON.stringify(fullSlowViewState)}, ${JSON.stringify(fastViewState)}, ${JSON.stringify(fastCarryForward)}, ${JSON.stringify(clientInitData)}` : `${JSON.stringify(fastViewState)}, ${JSON.stringify(fastCarryForward)}, ${JSON.stringify(clientInitData)}`;
440
- const tTotal = Date.now() - t0;
441
- const tLoadMs = tLoad - tLoadStart;
442
- const serverTiming = {
443
- cache: tCache,
444
- parts: tParts,
445
- fast: tFast - tData,
446
- load: tLoadMs,
447
- ssr: tSsr - tSsrStart,
448
- total: tTotal
449
- };
450
- write(`
451
- <script>console.log('[jay] server: cache=${serverTiming.cache}ms parts=${serverTiming.parts}ms fast=${serverTiming.fast}ms load=${serverTiming.load}ms ssr=${serverTiming.ssr}ms total=${serverTiming.total}ms')<\/script>
452
- <script type="module">
453
- import { init } from '${clientBundleUrl}';
454
- const _t=performance.now();
455
- await init(${initArgs});
456
- console.log('[jay] hydrate: '+(performance.now()-_t).toFixed(1)+'ms');
457
- <\/script>${bodyScriptsHtml ? "\n " + bodyScriptsHtml : ""}
458
- </body>
459
- </html>`);
460
- controller.close();
461
- const timingParts = [
462
- `cache: ${tCache}ms`,
463
- `parts: ${tParts}ms`,
464
- `fast: ${serverTiming.fast}ms`,
465
- `load: ${tLoadMs}ms`,
466
- `ssr: ${serverTiming.ssr}ms`
467
- ];
468
- console.log(`GET ${match.pathname} [${timingParts.join(" | ")}] ${tTotal}ms`);
469
- }
470
- });
471
- const responseHeaders = fastResult.responseHeaders || {};
472
- return new Response(stream, {
473
- headers: { "Content-Type": "text/html; charset=utf-8", ...responseHeaders }
474
- });
475
- }
476
- const ACTION_PREFIX = "/_jay/actions/";
477
- function isActionRequest(pathname) {
478
- return pathname.startsWith(ACTION_PREFIX);
479
- }
480
- async function fetchActionRequest(request, registry = actionRegistry) {
481
- const url = new URL(request.url);
482
- const actionName = url.pathname.slice(ACTION_PREFIX.length);
483
- if (!actionName) {
484
- return jsonResponse(400, {
485
- success: false,
486
- error: {
487
- code: "MISSING_ACTION_NAME",
488
- message: "Action name is required",
489
- isActionError: false
490
- }
491
- });
492
- }
493
- const action = registry.get(actionName);
494
- if (!action) {
495
- return jsonResponse(404, {
496
- success: false,
497
- error: {
498
- code: "ACTION_NOT_FOUND",
499
- message: `Action '${actionName}' is not registered`,
500
- isActionError: false
501
- }
502
- });
503
- }
504
- const requestMethod = request.method.toUpperCase();
505
- if (requestMethod !== action.method) {
506
- return jsonResponse(405, {
507
- success: false,
508
- error: {
509
- code: "METHOD_NOT_ALLOWED",
510
- message: `Action '${actionName}' expects ${action.method}, got ${requestMethod}`,
511
- isActionError: false
512
- }
513
- });
514
- }
515
- let input;
516
- try {
517
- if (requestMethod === "GET") {
518
- const inputParam = url.searchParams.get("_input");
519
- if (inputParam) {
520
- input = JSON.parse(inputParam);
521
- } else {
522
- input = Object.fromEntries(url.searchParams.entries());
523
- delete input._input;
524
- }
525
- } else {
526
- const text = await request.text();
527
- input = text ? JSON.parse(text) : {};
528
- }
529
- } catch {
530
- return jsonResponse(400, {
531
- success: false,
532
- error: {
533
- code: "INVALID_INPUT",
534
- message: "Failed to parse request input",
535
- isActionError: false
536
- }
537
- });
538
- }
539
- if (registry.isStreaming(actionName)) {
540
- const encoder = new TextEncoder();
541
- const stream = new ReadableStream({
542
- async start(controller) {
543
- try {
544
- const generator = registry.executeStream(actionName, input);
545
- for await (const chunk of generator) {
546
- controller.enqueue(encoder.encode(JSON.stringify({ chunk }) + "\n"));
547
- }
548
- controller.enqueue(encoder.encode(JSON.stringify({ done: true }) + "\n"));
549
- } catch (err) {
550
- controller.enqueue(
551
- encoder.encode(JSON.stringify({ error: err.message }) + "\n")
552
- );
553
- }
554
- controller.close();
555
- }
556
- });
557
- return new Response(stream, {
558
- headers: { "Content-Type": "application/x-ndjson" }
559
- });
560
- }
561
- const result = await registry.execute(actionName, input);
562
- if (result.success) {
563
- const headers = {};
564
- if (requestMethod === "GET") {
565
- const cacheHeaders = registry.getCacheHeaders(actionName);
566
- if (cacheHeaders) {
567
- headers["Cache-Control"] = cacheHeaders;
568
- }
569
- }
570
- return jsonResponse(200, { success: true, data: result.data }, headers);
571
- } else {
572
- const statusCode = getStatusCode(result.error.code, result.error.isActionError);
573
- return jsonResponse(statusCode, { success: false, error: result.error });
574
- }
575
- }
576
- function jsonResponse(status, body, extraHeaders = {}) {
577
- return new Response(JSON.stringify(body), {
578
- status,
579
- headers: { "Content-Type": "application/json", ...extraHeaders }
580
- });
581
- }
582
- async function registerActionsFromManifest(actions, buildDir, registry = actionRegistry) {
583
- const logger = getLogger();
584
- let count = 0;
585
- for (const entry of actions) {
586
- try {
587
- const modulePath = entry.isPlugin ? entry.packageName : `${buildDir}/${entry.serverModule}`;
588
- const mod = await import(modulePath);
589
- for (const [, exported] of Object.entries(mod)) {
590
- if (isJayAction(exported)) {
591
- registry.register(exported);
592
- count++;
593
- } else if (isJayStreamAction(exported)) {
594
- registry.registerStream(exported);
595
- count++;
596
- }
597
- }
598
- } catch (err) {
599
- logger.error(
600
- `[Server] Failed to load action module ${entry.serverModule}: ${err.message}`
601
- );
602
- }
603
- }
604
- logger.info(`[Server] Registered ${count} actions`);
605
- }
606
- async function registerActionsFromModules(modules, registry = actionRegistry) {
607
- const logger = getLogger();
608
- let count = 0;
609
- for (const { module, name } of modules) {
610
- for (const [, exported] of Object.entries(module)) {
611
- if (isJayAction(exported)) {
612
- registry.register(exported);
613
- count++;
614
- } else if (isJayStreamAction(exported)) {
615
- registry.registerStream(exported);
616
- count++;
617
- }
618
- }
619
- }
620
- logger.info(`[Server] Registered ${count} actions from pre-imported modules`);
621
- }
622
- function getStatusCode(code, isActionError) {
623
- if (isActionError) return 422;
624
- switch (code) {
625
- case "ACTION_NOT_FOUND":
626
- return 404;
627
- case "INVALID_INPUT":
628
- case "VALIDATION_ERROR":
629
- return 400;
630
- case "UNAUTHORIZED":
631
- return 401;
632
- case "FORBIDDEN":
633
- return 403;
634
- default:
635
- return 500;
636
- }
637
- }
638
- const MIME_TYPES = {
639
- ".js": "application/javascript",
640
- ".css": "text/css",
641
- ".json": "application/json",
642
- ".html": "text/html",
643
- ".svg": "image/svg+xml",
644
- ".png": "image/png",
645
- ".jpg": "image/jpeg",
646
- ".jpeg": "image/jpeg",
647
- ".gif": "image/gif",
648
- ".ico": "image/x-icon",
649
- ".woff2": "font/woff2",
650
- ".woff": "font/woff",
651
- ".ttf": "font/ttf",
652
- ".webp": "image/webp",
653
- ".xml": "application/xml",
654
- ".txt": "text/plain",
655
- // Agent-readiness resources (DL#217): SKILL.md + packaged skill archives.
656
- ".md": "text/markdown",
657
- ".gz": "application/gzip",
658
- // `.tar.gz` resolves via its last extension `.gz`
659
- ".tar": "application/x-tar"
660
- };
661
- async function fetchStaticFile(pathname, frontendDir) {
662
- const normalizedBase = path.resolve(frontendDir);
663
- for (const candidate of [path.join(frontendDir, pathname)]) {
664
- const normalizedFile = path.resolve(candidate);
665
- if (!normalizedFile.startsWith(normalizedBase)) continue;
666
- try {
667
- const content = await fs.readFile(candidate);
668
- const ext = path.extname(candidate);
669
- const contentType = MIME_TYPES[ext] || "application/octet-stream";
670
- const isHashed = /[-][a-zA-Z0-9_-]{6,}\./.test(path.basename(candidate));
671
- const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "public, max-age=3600";
672
- const headers = {
673
- "Content-Type": contentType,
674
- "Content-Length": String(content.length),
675
- "Cache-Control": cacheControl
676
- };
677
- if (pathname.startsWith("/.well-known/")) {
678
- headers["Access-Control-Allow-Origin"] = "*";
679
- }
680
- return new Response(new Uint8Array(content), { headers });
681
- } catch {
682
- }
683
- }
684
- return null;
685
- }
686
- async function initializeServicesFromModules(plugins, label) {
687
- const logger = getLogger();
688
- for (const plugin of plugins) {
689
- try {
690
- if (plugin.init?._serverInit) {
691
- logger.info(`[${label}] Running plugin init: ${plugin.name}`);
692
- const data = await plugin.init._serverInit();
693
- if (data) setClientInitData(plugin.name, data);
694
- }
695
- } catch (err) {
696
- logger.warn(`[${label}] Plugin init failed: ${plugin.name}: ${err.message}`);
697
- }
698
- }
699
- }
700
- async function initializeServices(buildDir, projectRoot, label) {
701
- const logger = getLogger();
702
- const { discoverPluginsWithInit, sortPluginsByDependencies } = await import("@jay-framework/stack-server-runtime");
703
- try {
704
- const pluginsWithInit = sortPluginsByDependencies(
705
- await discoverPluginsWithInit({ projectRoot })
706
- );
707
- for (const pluginInit of pluginsWithInit) {
708
- try {
709
- let modulePath;
710
- if (pluginInit.isLocal) {
711
- const pluginDirName = path.basename(pluginInit.pluginPath);
712
- const initModule = pluginInit.initModule || "index";
713
- modulePath = path.join(
714
- buildDir,
715
- "server",
716
- "plugins",
717
- pluginDirName,
718
- initModule + ".js"
719
- );
720
- } else {
721
- modulePath = pluginInit.packageName;
722
- }
723
- const pluginModule = await import(modulePath);
724
- const init = pluginModule.init || pluginModule[pluginInit.initExport || "init"];
725
- if (init?._serverInit) {
726
- logger.info(`[${label}] Running plugin init: ${pluginInit.name}`);
727
- const data = await init._serverInit();
728
- if (data) setClientInitData(pluginInit.name, data);
729
- }
730
- } catch (err) {
731
- logger.warn(`[${label}] Plugin init failed: ${pluginInit.name}: ${err.message}`);
732
- }
733
- }
734
- } catch {
735
- }
736
- const initModulePath = path.join(buildDir, "server", "init.js");
737
- try {
738
- const initModule = await import(initModulePath);
739
- const init = initModule.init || initModule.default;
740
- if (init?._serverInit) {
741
- logger.info(`[${label}] Running server init...`);
742
- const data = await init._serverInit();
743
- if (data) setClientInitData("project", data);
744
- }
745
- } catch {
746
- }
747
- }
748
- export {
749
- FilesystemArtifactStore as F,
750
- fetchPageRequest as a,
751
- fetchStaticFile as b,
752
- initializeServicesFromModules as c,
753
- isActionRequest as d,
754
- registerActionsFromModules as e,
755
- fetchActionRequest as f,
756
- renderErrorPage as g,
757
- initializeServices as i,
758
- loadPagePartsFromConfig as l,
759
- matchRequest as m,
760
- registerActionsFromManifest as r
761
- };