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