@jay-framework/dev-server 0.11.0 → 0.13.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.
Files changed (3) hide show
  1. package/dist/index.d.ts +49 -2
  2. package/dist/index.js +513 -100
  3. package/package.json +14 -13
package/dist/index.d.ts CHANGED
@@ -2,8 +2,10 @@ import { ViteDevServer, Connect } from 'vite';
2
2
  import { JayRoute } from '@jay-framework/stack-route-scanner';
3
3
  import { RequestHandler } from 'express-serve-static-core';
4
4
  import { JayRollupConfig } from '@jay-framework/rollup-plugin';
5
+ import { LogLevel } from '@jay-framework/logger';
5
6
  import { ProjectClientInitInfo, PluginWithInit, ActionRegistry } from '@jay-framework/stack-server-runtime';
6
7
  import { RequestHandler as RequestHandler$1 } from 'express';
8
+ import { JayRollupConfig as JayRollupConfig$1 } from '@jay-framework/compiler-jay-stack';
7
9
 
8
10
  interface DevServerOptions {
9
11
  publicBaseUrlPath?: string;
@@ -23,6 +25,11 @@ interface DevServerOptions {
23
25
  * The automation API is available at `window.__jay.automation` and via `AUTOMATION_CONTEXT`.
24
26
  */
25
27
  disableAutomation?: boolean;
28
+ /**
29
+ * Log level for dev server output.
30
+ * Controls both Jay logging and Vite logging.
31
+ */
32
+ logLevel?: LogLevel;
26
33
  }
27
34
 
28
35
  /**
@@ -100,7 +107,7 @@ interface DevServer {
100
107
  routes: DevServerRoute[];
101
108
  lifecycleManager: ServiceLifecycleManager;
102
109
  }
103
- declare function mkDevServer(options: DevServerOptions): Promise<DevServer>;
110
+ declare function mkDevServer(rawOptions: DevServerOptions): Promise<DevServer>;
104
111
 
105
112
  /**
106
113
  * Action Router for Jay Stack dev server.
@@ -151,4 +158,44 @@ declare function createActionRouter(options?: ActionRouterOptions): RequestHandl
151
158
  */
152
159
  declare function actionBodyParser(): RequestHandler$1;
153
160
 
154
- export { ACTION_ENDPOINT_BASE, type ActionRouterOptions, type DevServer, type DevServerOptions, type DevServerRoute, actionBodyParser, createActionRouter, mkDevServer };
161
+ /**
162
+ * Vite Factory
163
+ *
164
+ * Single source of truth for creating Vite servers.
165
+ * Used by both the dev-server and CLI commands.
166
+ */
167
+
168
+ interface CreateViteServerOptions {
169
+ /** Project root directory */
170
+ projectRoot: string;
171
+ /** Root directory for pages (defaults to projectRoot) */
172
+ pagesRoot?: string;
173
+ /** Base URL path for public assets */
174
+ base?: string;
175
+ /** Jay Stack compiler config (optional, will use defaults if not provided) */
176
+ jayRollupConfig?: JayRollupConfig$1;
177
+ /** Log level (defaults to 'info' for dev-server, 'warn' for CLI) */
178
+ logLevel?: 'info' | 'warn' | 'error' | 'silent';
179
+ /** Whether to clear screen on rebuild */
180
+ clearScreen?: boolean;
181
+ }
182
+ /**
183
+ * Creates a Vite server configured for Jay Stack.
184
+ *
185
+ * This is the single source of truth for Vite configuration.
186
+ * Both dev-server and CLI use this function.
187
+ */
188
+ declare function createViteServer(options: CreateViteServerOptions): Promise<ViteDevServer>;
189
+ /**
190
+ * Creates a minimal Vite server for CLI usage.
191
+ *
192
+ * This is a convenience wrapper around createViteServer with CLI-appropriate defaults.
193
+ * Disables dependency optimization and ignores the build/ folder to avoid errors
194
+ * from stale build artifacts (e.g., build/client-scripts/ referencing build/slow-render-cache/).
195
+ */
196
+ declare function createViteForCli(options: {
197
+ projectRoot: string;
198
+ tsConfigFilePath?: string;
199
+ }): Promise<ViteDevServer>;
200
+
201
+ export { ACTION_ENDPOINT_BASE, type ActionRouterOptions, type CreateViteServerOptions, type DevServer, type DevServerOptions, type DevServerRoute, actionBodyParser, createActionRouter, createViteForCli, createViteServer, mkDevServer };
package/dist/index.js CHANGED
@@ -5,16 +5,17 @@ var __publicField = (obj, key, value) => {
5
5
  return value;
6
6
  };
7
7
  import { createServer } from "vite";
8
- import { scanRoutes, routeToExpressRoute } from "@jay-framework/stack-route-scanner";
9
- import { discoverPluginsWithInit, sortPluginsByDependencies, executePluginServerInits, runInitCallbacks, actionRegistry, discoverAndRegisterActions, discoverAllPluginActions, runShutdownCallbacks, clearLifecycleCallbacks, clearServiceRegistry, clearClientInitData, DevSlowlyChangingPhase, SlowRenderCache, preparePluginClientInits, loadPageParts, renderFastChangingData, generateClientScript, getClientInitData } from "@jay-framework/stack-server-runtime";
10
8
  import { jayRuntime } from "@jay-framework/vite-plugin";
11
9
  import { createRequire } from "module";
12
10
  import "@jay-framework/compiler-shared";
13
11
  import * as path from "node:path";
14
12
  import path__default from "node:path";
15
- import { parseContract, slowRenderTransform } from "@jay-framework/compiler-jay-html";
13
+ import { discoverHeadlessInstances, parseContract, slowRenderTransform, JAY_IMPORT_RESOLVER, resolveHeadlessInstances } from "@jay-framework/compiler-jay-html";
14
+ import { getLogger, getDevLogger } from "@jay-framework/logger";
16
15
  import { createRequire as createRequire$1 } from "node:module";
17
16
  import * as fs from "node:fs";
17
+ import { scanRoutes, routeToExpressRoute } from "@jay-framework/stack-route-scanner";
18
+ import { discoverPluginsWithInit, sortPluginsByDependencies, executePluginServerInits, runInitCallbacks, actionRegistry, discoverAndRegisterActions, discoverAllPluginActions, runShutdownCallbacks, clearLifecycleCallbacks, clearServiceRegistry, clearClientInitData, DevSlowlyChangingPhase, SlowRenderCache, preparePluginClientInits, getServiceRegistry, materializeContracts, loadPageParts, renderFastChangingData, validateForEachInstances, slowRenderInstances, generateClientScript, getClientInitData, resolveServices } from "@jay-framework/stack-server-runtime";
18
19
  import fs$1 from "node:fs/promises";
19
20
  import { pathToFileURL } from "node:url";
20
21
  const s$1 = createRequire(import.meta.url), e$1 = s$1("typescript"), c$1 = new Proxy(e$1, {
@@ -1021,7 +1022,7 @@ function createImportChainTracker(options = {}) {
1021
1022
  importChain.clear();
1022
1023
  detectedServerModules.clear();
1023
1024
  if (verbose) {
1024
- console.log("[import-chain-tracker] Build started, tracking imports...");
1025
+ getLogger().info("[import-chain-tracker] Build started, tracking imports...");
1025
1026
  }
1026
1027
  },
1027
1028
  resolveId(source, importer, options2) {
@@ -1033,7 +1034,7 @@ function createImportChainTracker(options = {}) {
1033
1034
  }
1034
1035
  if (importer) {
1035
1036
  if (verbose) {
1036
- console.log(
1037
+ getLogger().info(
1037
1038
  `[import-chain-tracker] ${shortenPath(importer)} imports ${source}`
1038
1039
  );
1039
1040
  }
@@ -1050,14 +1051,14 @@ function createImportChainTracker(options = {}) {
1050
1051
  if (isServerOnlyModule(id)) {
1051
1052
  detectedServerModules.add(id);
1052
1053
  const chain = buildImportChain(id);
1053
- console.error(
1054
+ getLogger().error(
1054
1055
  `
1055
1056
  [import-chain-tracker] ⚠️ Server-only module detected in client build!`
1056
1057
  );
1057
- console.error(`Module: ${shortenPath(id)}`);
1058
- console.error(`Import chain:`);
1059
- console.error(formatChain(chain));
1060
- console.error("");
1058
+ getLogger().error(`Module: ${shortenPath(id)}`);
1059
+ getLogger().error(`Import chain:`);
1060
+ getLogger().error(formatChain(chain));
1061
+ getLogger().error("");
1061
1062
  }
1062
1063
  const importRegex = /import\s+(?:(?:\{[^}]*\}|[^{}\s,]+)\s+from\s+)?['"]([^'"]+)['"]/g;
1063
1064
  let match;
@@ -1067,16 +1068,18 @@ function createImportChainTracker(options = {}) {
1067
1068
  if (isServerOnlyModule(importedModule)) {
1068
1069
  if (!detectedServerModules.has(importedModule)) {
1069
1070
  detectedServerModules.add(importedModule);
1070
- console.error(
1071
+ getLogger().error(
1071
1072
  `
1072
1073
  [import-chain-tracker] ⚠️ Server-only import detected in client build!`
1073
1074
  );
1074
- console.error(`Module "${importedModule}" imported by: ${shortenPath(id)}`);
1075
+ getLogger().error(
1076
+ `Module "${importedModule}" imported by: ${shortenPath(id)}`
1077
+ );
1075
1078
  const chain = buildImportChain(id);
1076
1079
  chain.push(importedModule);
1077
- console.error(`Import chain:`);
1078
- console.error(formatChain(chain));
1079
- console.error("");
1080
+ getLogger().error(`Import chain:`);
1081
+ getLogger().error(formatChain(chain));
1082
+ getLogger().error("");
1080
1083
  }
1081
1084
  }
1082
1085
  }
@@ -1084,21 +1087,21 @@ function createImportChainTracker(options = {}) {
1084
1087
  },
1085
1088
  buildEnd() {
1086
1089
  if (detectedServerModules.size > 0) {
1087
- console.warn(
1090
+ getLogger().warn(
1088
1091
  `
1089
1092
  [import-chain-tracker] ⚠️ ${detectedServerModules.size} server-only module(s) detected during transform:`
1090
1093
  );
1091
1094
  for (const mod of detectedServerModules) {
1092
- console.warn(` - ${mod}`);
1095
+ getLogger().warn(` - ${mod}`);
1093
1096
  }
1094
- console.warn(
1097
+ getLogger().warn(
1095
1098
  "\nNote: These may be stripped by the code-split transform if only used in .withServer()."
1096
1099
  );
1097
- console.warn(
1100
+ getLogger().warn(
1098
1101
  'If build fails with "not exported" errors, check the import chains above.\n'
1099
1102
  );
1100
1103
  } else if (verbose) {
1101
- console.log(
1104
+ getLogger().info(
1102
1105
  "[import-chain-tracker] ✅ No server-only modules detected in client build"
1103
1106
  );
1104
1107
  }
@@ -1165,7 +1168,7 @@ function transformImports(options) {
1165
1168
  hasChanges = true;
1166
1169
  const newSource = `${packageName}/client`;
1167
1170
  if (verbose) {
1168
- console.log(
1171
+ getLogger().info(
1169
1172
  `[plugin-client-import] Rewriting import ${source} -> ${newSource} (in ${path.basename(filePath)})`
1170
1173
  );
1171
1174
  }
@@ -1182,7 +1185,7 @@ function transformImports(options) {
1182
1185
  hasChanges = true;
1183
1186
  const newSource = `${packageName}/client`;
1184
1187
  if (verbose) {
1185
- console.log(
1188
+ getLogger().info(
1186
1189
  `[plugin-client-import] Rewriting export ${source} -> ${newSource} (in ${path.basename(filePath)})`
1187
1190
  );
1188
1191
  }
@@ -1258,7 +1261,7 @@ function jayStackCompiler(options = {}) {
1258
1261
  try {
1259
1262
  return transformJayStackBuilder(code, id, environment);
1260
1263
  } catch (error) {
1261
- console.error(`[jay-stack:code-split] Error transforming ${id}:`, error);
1264
+ getLogger().error(`[jay-stack:code-split] Error transforming ${id}: ${error}`);
1262
1265
  return null;
1263
1266
  }
1264
1267
  }
@@ -1318,12 +1321,14 @@ function jayStackCompiler(options = {}) {
1318
1321
  try {
1319
1322
  code = await fs.promises.readFile(actualPath, "utf-8");
1320
1323
  } catch (err) {
1321
- console.error(`[action-transform] Could not read ${actualPath}:`, err);
1324
+ getLogger().error(
1325
+ `[action-transform] Could not read ${actualPath}: ${err}`
1326
+ );
1322
1327
  return null;
1323
1328
  }
1324
1329
  const actions = extractActionsFromSource(code, actualPath);
1325
1330
  if (actions.length === 0) {
1326
- console.warn(`[action-transform] No actions found in ${actualPath}`);
1331
+ getLogger().warn(`[action-transform] No actions found in ${actualPath}`);
1327
1332
  return null;
1328
1333
  }
1329
1334
  const lines = [
@@ -1350,6 +1355,67 @@ function jayStackCompiler(options = {}) {
1350
1355
  );
1351
1356
  return plugins;
1352
1357
  }
1358
+ async function createViteServer(options) {
1359
+ const {
1360
+ projectRoot,
1361
+ pagesRoot = projectRoot,
1362
+ base,
1363
+ jayRollupConfig = { tsConfigFilePath: path__default.join(projectRoot, "tsconfig.json") },
1364
+ logLevel = "info",
1365
+ clearScreen = true
1366
+ } = options;
1367
+ const vite = await createServer({
1368
+ // Don't start HTTP server - we use middleware mode
1369
+ server: { middlewareMode: true },
1370
+ // Use Jay Stack compiler for .jay-html and other custom transforms
1371
+ plugins: [...jayStackCompiler(jayRollupConfig)],
1372
+ // Custom app type (no default middleware)
1373
+ appType: "custom",
1374
+ // Base URL path
1375
+ base,
1376
+ // Root directory for module resolution
1377
+ root: pagesRoot,
1378
+ // SSR configuration
1379
+ ssr: {
1380
+ // Mark jay-framework packages as external so Vite uses Node's require
1381
+ // This ensures all packages share the same module instances (Symbol identity)
1382
+ external: ["@jay-framework/stack-server-runtime", "@jay-framework/fullstack-component"]
1383
+ },
1384
+ // Disable automatic entry point discovery for pre-bundling —
1385
+ // we run in middleware mode with no HTML files, so Vite can't auto-detect entries
1386
+ optimizeDeps: {
1387
+ entries: []
1388
+ },
1389
+ // Logging
1390
+ logLevel,
1391
+ clearScreen
1392
+ });
1393
+ return vite;
1394
+ }
1395
+ async function createViteForCli(options) {
1396
+ const { projectRoot, tsConfigFilePath = path__default.join(projectRoot, "tsconfig.json") } = options;
1397
+ const vite = await createServer({
1398
+ server: {
1399
+ middlewareMode: true,
1400
+ watch: {
1401
+ ignored: ["**/build/**"]
1402
+ }
1403
+ },
1404
+ plugins: [...jayStackCompiler({ tsConfigFilePath })],
1405
+ appType: "custom",
1406
+ root: projectRoot,
1407
+ ssr: {
1408
+ external: ["@jay-framework/stack-server-runtime", "@jay-framework/fullstack-component"]
1409
+ },
1410
+ // Disable dependency optimization — CLI only uses SSR, no browser bundles
1411
+ optimizeDeps: {
1412
+ entries: []
1413
+ },
1414
+ logLevel: "warn",
1415
+ clearScreen: false
1416
+ });
1417
+ return vite;
1418
+ }
1353
1419
  class ServiceLifecycleManager {
1354
1420
  constructor(projectRoot, sourceBase = "src") {
1355
1421
  /** Path to project's lib/init.ts (makeJayInit pattern) */
@@ -1388,8 +1454,9 @@ class ServiceLifecycleManager {
1388
1454
  * 4. Auto-discovering and registering actions
1389
1455
  */
1390
1456
  async initialize() {
1457
+ const log = getLogger();
1391
1458
  if (this.isInitialized) {
1392
- console.warn("[Services] Already initialized, skipping...");
1459
+ log.warn("[Services] Already initialized, skipping...");
1393
1460
  return;
1394
1461
  }
1395
1462
  this.projectInitFilePath = this.findProjectInitFile();
@@ -1399,18 +1466,18 @@ class ServiceLifecycleManager {
1399
1466
  });
1400
1467
  this.pluginsWithInit = sortPluginsByDependencies(discoveredPlugins);
1401
1468
  if (this.pluginsWithInit.length > 0) {
1402
- console.log(
1469
+ log.info(
1403
1470
  `[Services] Found ${this.pluginsWithInit.length} plugin(s) with init: ${this.pluginsWithInit.map((p) => p.name).join(", ")}`
1404
1471
  );
1405
1472
  }
1406
1473
  await executePluginServerInits(this.pluginsWithInit, this.viteServer ?? void 0, true);
1407
1474
  if (this.projectInitFilePath) {
1408
- console.log("[DevServer] Loading project init: src/init.ts");
1475
+ log.info("[DevServer] Loading project init: src/init.ts");
1409
1476
  try {
1410
1477
  if (this.viteServer) {
1411
1478
  const module = await this.viteServer.ssrLoadModule(this.projectInitFilePath);
1412
1479
  if (module.init?._serverInit) {
1413
- console.log("[DevServer] Running server init: project");
1480
+ log.info("[DevServer] Running server init: project");
1414
1481
  const { setClientInitData } = await import("@jay-framework/stack-server-runtime");
1415
1482
  const clientData = await module.init._serverInit();
1416
1483
  if (clientData !== void 0 && clientData !== null) {
@@ -1422,14 +1489,14 @@ class ServiceLifecycleManager {
1422
1489
  await import(fileUrl);
1423
1490
  }
1424
1491
  } catch (error) {
1425
- console.error("[Services] Failed to load project init:", error);
1492
+ log.error(`[Services] Failed to load project init: ${error}`);
1426
1493
  throw error;
1427
1494
  }
1428
1495
  } else {
1429
- console.log("[Services] No init.ts found, skipping project initialization");
1496
+ log.info("[Services] No init.ts found, skipping project initialization");
1430
1497
  }
1431
1498
  await runInitCallbacks();
1432
- console.log("[Services] Initialization complete");
1499
+ log.info("[Services] Initialization complete");
1433
1500
  await this.discoverActions();
1434
1501
  this.isInitialized = true;
1435
1502
  }
@@ -1437,6 +1504,7 @@ class ServiceLifecycleManager {
1437
1504
  * Auto-discovers and registers actions from project and plugins.
1438
1505
  */
1439
1506
  async discoverActions() {
1507
+ const log = getLogger();
1440
1508
  let totalActions = 0;
1441
1509
  try {
1442
1510
  const result = await discoverAndRegisterActions({
@@ -1448,7 +1516,7 @@ class ServiceLifecycleManager {
1448
1516
  });
1449
1517
  totalActions += result.actionCount;
1450
1518
  } catch (error) {
1451
- console.error("[Actions] Failed to auto-discover project actions:", error);
1519
+ log.error(`[Actions] Failed to auto-discover project actions: ${error}`);
1452
1520
  }
1453
1521
  try {
1454
1522
  const pluginActions = await discoverAllPluginActions({
@@ -1459,20 +1527,21 @@ class ServiceLifecycleManager {
1459
1527
  });
1460
1528
  totalActions += pluginActions.length;
1461
1529
  } catch (error) {
1462
- console.error("[Actions] Failed to auto-discover plugin actions:", error);
1530
+ log.error(`[Actions] Failed to auto-discover plugin actions: ${error}`);
1463
1531
  }
1464
1532
  if (totalActions > 0) {
1465
- console.log(`[Actions] Auto-registered ${totalActions} action(s) total`);
1533
+ log.info(`[Actions] Auto-registered ${totalActions} action(s) total`);
1466
1534
  }
1467
1535
  }
1468
1536
  /**
1469
1537
  * Shuts down services gracefully with timeout
1470
1538
  */
1471
1539
  async shutdown(timeoutMs = 5e3) {
1540
+ const log = getLogger();
1472
1541
  if (!this.isInitialized) {
1473
1542
  return;
1474
1543
  }
1475
- console.log("[Services] Shutting down...");
1544
+ log.info("[Services] Shutting down...");
1476
1545
  try {
1477
1546
  await Promise.race([
1478
1547
  runShutdownCallbacks(),
@@ -1480,12 +1549,12 @@ class ServiceLifecycleManager {
1480
1549
  (_, reject) => setTimeout(() => reject(new Error("Shutdown timeout")), timeoutMs)
1481
1550
  )
1482
1551
  ]);
1483
- console.log("[Services] Shutdown complete");
1552
+ log.info("[Services] Shutdown complete");
1484
1553
  } catch (error) {
1485
1554
  if (error.message === "Shutdown timeout") {
1486
- console.warn("[Services] Shutdown timed out after", timeoutMs, "ms");
1555
+ log.warn(`[Services] Shutdown timed out after ${timeoutMs}ms`);
1487
1556
  } else {
1488
- console.error("[Services] Shutdown error:", error);
1557
+ log.error(`[Services] Shutdown error: ${error}`);
1489
1558
  }
1490
1559
  } finally {
1491
1560
  this.isInitialized = false;
@@ -1495,7 +1564,8 @@ class ServiceLifecycleManager {
1495
1564
  * Hot reload: shutdown, clear caches, re-import, and re-initialize
1496
1565
  */
1497
1566
  async reload() {
1498
- console.log("[Services] Reloading services...");
1567
+ const log = getLogger();
1568
+ log.info("[Services] Reloading services...");
1499
1569
  await this.shutdown();
1500
1570
  clearLifecycleCallbacks();
1501
1571
  clearServiceRegistry();
@@ -1511,7 +1581,7 @@ class ServiceLifecycleManager {
1511
1581
  }
1512
1582
  this.isInitialized = false;
1513
1583
  await this.initialize();
1514
- console.log("[Services] Reload complete");
1584
+ log.info("[Services] Reload complete");
1515
1585
  }
1516
1586
  /**
1517
1587
  * Returns the path to the init file if found.
@@ -1792,7 +1862,12 @@ function filterPluginsForPage(allPluginClientInits, allPluginsWithInit, usedPack
1792
1862
  pluginsByPackage.set(plugin.packageName, plugin);
1793
1863
  }
1794
1864
  const expandedPackages = new Set(usedPackages);
1795
- const toProcess = [...usedPackages];
1865
+ for (const plugin of allPluginsWithInit) {
1866
+ if (plugin.global) {
1867
+ expandedPackages.add(plugin.packageName);
1868
+ }
1869
+ }
1870
+ const toProcess = [...expandedPackages];
1796
1871
  while (toProcess.length > 0) {
1797
1872
  const packageName = toProcess.pop();
1798
1873
  const plugin = pluginsByPackage.get(packageName);
@@ -1813,6 +1888,7 @@ function filterPluginsForPage(allPluginClientInits, allPluginsWithInit, usedPack
1813
1888
  function mkRoute(route, vite, slowlyPhase, options, slowRenderCache, projectInit, allPluginsWithInit = [], allPluginClientInits = []) {
1814
1889
  const routePath = routeToExpressRoute(route);
1815
1890
  const handler = async (req, res) => {
1891
+ const timing = getDevLogger()?.startRequest(req.method, req.path);
1816
1892
  try {
1817
1893
  const url = req.originalUrl.replace(options.publicBaseUrlPath, "");
1818
1894
  const pageParams = { ...route.inferredParams, ...req.params };
@@ -1826,7 +1902,7 @@ function mkRoute(route, vite, slowlyPhase, options, slowRenderCache, projectInit
1826
1902
  try {
1827
1903
  await fs$1.access(cachedEntry.preRenderedPath);
1828
1904
  } catch {
1829
- console.log(
1905
+ getLogger().info(
1830
1906
  `[SlowRender] Cached file missing, rebuilding: ${cachedEntry.preRenderedPath}`
1831
1907
  );
1832
1908
  await slowRenderCache.invalidate(route.jayHtmlPath);
@@ -1845,7 +1921,8 @@ function mkRoute(route, vite, slowlyPhase, options, slowRenderCache, projectInit
1845
1921
  allPluginsWithInit,
1846
1922
  projectInit,
1847
1923
  res,
1848
- url
1924
+ url,
1925
+ timing
1849
1926
  );
1850
1927
  } else if (useSlowRenderCache) {
1851
1928
  await handlePreRenderRequest(
@@ -1860,7 +1937,8 @@ function mkRoute(route, vite, slowlyPhase, options, slowRenderCache, projectInit
1860
1937
  allPluginsWithInit,
1861
1938
  projectInit,
1862
1939
  res,
1863
- url
1940
+ url,
1941
+ timing
1864
1942
  );
1865
1943
  } else {
1866
1944
  await handleDirectRequest(
@@ -1874,18 +1952,21 @@ function mkRoute(route, vite, slowlyPhase, options, slowRenderCache, projectInit
1874
1952
  allPluginsWithInit,
1875
1953
  projectInit,
1876
1954
  res,
1877
- url
1955
+ url,
1956
+ timing
1878
1957
  );
1879
1958
  }
1880
1959
  } catch (e2) {
1881
1960
  vite?.ssrFixStacktrace(e2);
1882
- console.log(e2.stack);
1961
+ getLogger().error(e2.stack);
1883
1962
  res.status(500).end(e2.stack);
1963
+ timing?.end();
1884
1964
  }
1885
1965
  };
1886
1966
  return { path: routePath, handler, fsRoute: route };
1887
1967
  }
1888
- async function handleCachedRequest(vite, route, options, cachedEntry, pageParams, pageProps, allPluginClientInits, allPluginsWithInit, projectInit, res, url) {
1968
+ async function handleCachedRequest(vite, route, options, cachedEntry, pageParams, pageProps, allPluginClientInits, allPluginsWithInit, projectInit, res, url, timing) {
1969
+ const loadStart = Date.now();
1889
1970
  const pagePartsResult = await loadPageParts(
1890
1971
  vite,
1891
1972
  route,
@@ -1894,9 +1975,11 @@ async function handleCachedRequest(vite, route, options, cachedEntry, pageParams
1894
1975
  options.jayRollupConfig,
1895
1976
  { preRenderedPath: cachedEntry.preRenderedPath }
1896
1977
  );
1978
+ timing?.recordViteSsr(Date.now() - loadStart);
1897
1979
  if (!pagePartsResult.val) {
1898
- console.log(pagePartsResult.validations.join("\n"));
1980
+ getLogger().info(pagePartsResult.validations.join("\n"));
1899
1981
  res.status(500).end(pagePartsResult.validations.join("\n"));
1982
+ timing?.end();
1900
1983
  return;
1901
1984
  }
1902
1985
  const { parts: pageParts, clientTrackByMap, usedPackages } = pagePartsResult.val;
@@ -1905,32 +1988,71 @@ async function handleCachedRequest(vite, route, options, cachedEntry, pageParams
1905
1988
  allPluginsWithInit,
1906
1989
  usedPackages
1907
1990
  );
1991
+ const fastStart = Date.now();
1908
1992
  const renderedFast = await renderFastChangingData(
1909
1993
  pageParams,
1910
1994
  pageProps,
1911
1995
  cachedEntry.carryForward,
1912
1996
  pageParts
1913
1997
  );
1998
+ timing?.recordFastRender(Date.now() - fastStart);
1914
1999
  if (renderedFast.kind !== "PhaseOutput") {
1915
2000
  handleOtherResponseCodes(res, renderedFast);
2001
+ timing?.end();
1916
2002
  return;
1917
2003
  }
2004
+ let fastViewState = renderedFast.rendered;
2005
+ let fastCarryForward = renderedFast.carryForward;
2006
+ const instancePhaseData = cachedEntry.carryForward?.__instances;
2007
+ const headlessComps = pagePartsResult.val.headlessInstanceComponents;
2008
+ if (instancePhaseData && headlessComps.length > 0) {
2009
+ const instanceFastResult = await renderFastChangingDataForInstances(
2010
+ instancePhaseData,
2011
+ headlessComps
2012
+ );
2013
+ if (instanceFastResult) {
2014
+ fastViewState = {
2015
+ ...fastViewState,
2016
+ __headlessInstances: instanceFastResult.viewStates
2017
+ };
2018
+ fastCarryForward = {
2019
+ ...fastCarryForward,
2020
+ __headlessInstances: instanceFastResult.carryForwards
2021
+ };
2022
+ }
2023
+ if (instancePhaseData.forEachInstances && instancePhaseData.forEachInstances.length > 0) {
2024
+ const forEachResult = await renderFastChangingDataForForEachInstances(
2025
+ instancePhaseData.forEachInstances,
2026
+ headlessComps,
2027
+ fastViewState
2028
+ );
2029
+ if (forEachResult) {
2030
+ const existingHeadless = fastViewState.__headlessInstances || {};
2031
+ fastViewState = {
2032
+ ...fastViewState,
2033
+ __headlessInstances: { ...existingHeadless, ...forEachResult }
2034
+ };
2035
+ }
2036
+ }
2037
+ }
1918
2038
  await sendResponse(
1919
2039
  vite,
1920
2040
  res,
1921
2041
  url,
1922
2042
  cachedEntry.preRenderedPath,
1923
2043
  pageParts,
1924
- renderedFast.rendered,
1925
- renderedFast.carryForward,
2044
+ fastViewState,
2045
+ fastCarryForward,
1926
2046
  clientTrackByMap,
1927
2047
  projectInit,
1928
2048
  pluginsForPage,
1929
2049
  options,
1930
- cachedEntry.slowViewState
2050
+ cachedEntry.slowViewState,
2051
+ timing
1931
2052
  );
1932
2053
  }
1933
- async function handlePreRenderRequest(vite, route, options, slowlyPhase, slowRenderCache, pageParams, pageProps, allPluginClientInits, allPluginsWithInit, projectInit, res, url) {
2054
+ async function handlePreRenderRequest(vite, route, options, slowlyPhase, slowRenderCache, pageParams, pageProps, allPluginClientInits, allPluginsWithInit, projectInit, res, url, timing) {
2055
+ const loadStart = Date.now();
1934
2056
  const initialPartsResult = await loadPageParts(
1935
2057
  vite,
1936
2058
  route,
@@ -1938,35 +2060,61 @@ async function handlePreRenderRequest(vite, route, options, slowlyPhase, slowRen
1938
2060
  options.projectRootFolder,
1939
2061
  options.jayRollupConfig
1940
2062
  );
2063
+ timing?.recordViteSsr(Date.now() - loadStart);
1941
2064
  if (!initialPartsResult.val) {
1942
- console.log(initialPartsResult.validations.join("\n"));
2065
+ getLogger().info(initialPartsResult.validations.join("\n"));
1943
2066
  res.status(500).end(initialPartsResult.validations.join("\n"));
2067
+ timing?.end();
1944
2068
  return;
1945
2069
  }
2070
+ const slowStart = Date.now();
1946
2071
  const renderedSlowly = await slowlyPhase.runSlowlyForPage(
1947
2072
  pageParams,
1948
2073
  pageProps,
1949
2074
  initialPartsResult.val.parts
1950
2075
  );
1951
2076
  if (renderedSlowly.kind !== "PhaseOutput") {
2077
+ timing?.recordSlowRender(Date.now() - slowStart);
1952
2078
  if (renderedSlowly.kind === "ClientError") {
1953
2079
  handleOtherResponseCodes(res, renderedSlowly);
1954
2080
  }
2081
+ timing?.end();
1955
2082
  return;
1956
2083
  }
1957
- const preRenderedContent = await preRenderJayHtml(route, renderedSlowly.rendered);
1958
- if (!preRenderedContent) {
2084
+ const preRenderResult = await preRenderJayHtml(
2085
+ route,
2086
+ renderedSlowly.rendered,
2087
+ initialPartsResult.val.headlessContracts,
2088
+ initialPartsResult.val.headlessInstanceComponents
2089
+ );
2090
+ timing?.recordSlowRender(Date.now() - slowStart);
2091
+ if (!preRenderResult) {
1959
2092
  res.status(500).end("Failed to pre-render jay-html");
2093
+ timing?.end();
1960
2094
  return;
1961
2095
  }
2096
+ let instancePhaseDataForCache = preRenderResult.instancePhaseData;
2097
+ if (instancePhaseDataForCache && preRenderResult.forEachInstances) {
2098
+ instancePhaseDataForCache = {
2099
+ ...instancePhaseDataForCache,
2100
+ forEachInstances: preRenderResult.forEachInstances
2101
+ };
2102
+ } else if (preRenderResult.forEachInstances) {
2103
+ instancePhaseDataForCache = {
2104
+ discovered: [],
2105
+ carryForwards: {},
2106
+ forEachInstances: preRenderResult.forEachInstances
2107
+ };
2108
+ }
2109
+ const carryForward = instancePhaseDataForCache ? { ...renderedSlowly.carryForward, __instances: instancePhaseDataForCache } : renderedSlowly.carryForward;
1962
2110
  const preRenderedPath = await slowRenderCache.set(
1963
2111
  route.jayHtmlPath,
1964
2112
  pageParams,
1965
- preRenderedContent,
2113
+ preRenderResult.preRenderedJayHtml,
1966
2114
  renderedSlowly.rendered,
1967
- renderedSlowly.carryForward
2115
+ carryForward
1968
2116
  );
1969
- console.log(`[SlowRender] Cached pre-rendered jay-html at ${preRenderedPath}`);
2117
+ getLogger().info(`[SlowRender] Cached pre-rendered jay-html at ${preRenderedPath}`);
1970
2118
  const pagePartsResult = await loadPageParts(
1971
2119
  vite,
1972
2120
  route,
@@ -1976,8 +2124,9 @@ async function handlePreRenderRequest(vite, route, options, slowlyPhase, slowRen
1976
2124
  { preRenderedPath }
1977
2125
  );
1978
2126
  if (!pagePartsResult.val) {
1979
- console.log(pagePartsResult.validations.join("\n"));
2127
+ getLogger().info(pagePartsResult.validations.join("\n"));
1980
2128
  res.status(500).end(pagePartsResult.validations.join("\n"));
2129
+ timing?.end();
1981
2130
  return;
1982
2131
  }
1983
2132
  const { parts: pageParts, clientTrackByMap, usedPackages } = pagePartsResult.val;
@@ -1986,32 +2135,71 @@ async function handlePreRenderRequest(vite, route, options, slowlyPhase, slowRen
1986
2135
  allPluginsWithInit,
1987
2136
  usedPackages
1988
2137
  );
2138
+ const fastStart = Date.now();
1989
2139
  const renderedFast = await renderFastChangingData(
1990
2140
  pageParams,
1991
2141
  pageProps,
1992
- renderedSlowly.carryForward,
2142
+ carryForward,
1993
2143
  pageParts
1994
2144
  );
2145
+ timing?.recordFastRender(Date.now() - fastStart);
1995
2146
  if (renderedFast.kind !== "PhaseOutput") {
1996
2147
  handleOtherResponseCodes(res, renderedFast);
2148
+ timing?.end();
1997
2149
  return;
1998
2150
  }
2151
+ let fastViewState = renderedFast.rendered;
2152
+ let fastCarryForward = renderedFast.carryForward;
2153
+ const instancePhaseData = carryForward?.__instances;
2154
+ const headlessComps = pagePartsResult.val.headlessInstanceComponents;
2155
+ if (instancePhaseData && headlessComps.length > 0) {
2156
+ const instanceFastResult = await renderFastChangingDataForInstances(
2157
+ instancePhaseData,
2158
+ headlessComps
2159
+ );
2160
+ if (instanceFastResult) {
2161
+ fastViewState = {
2162
+ ...fastViewState,
2163
+ __headlessInstances: instanceFastResult.viewStates
2164
+ };
2165
+ fastCarryForward = {
2166
+ ...fastCarryForward,
2167
+ __headlessInstances: instanceFastResult.carryForwards
2168
+ };
2169
+ }
2170
+ if (instancePhaseData.forEachInstances && instancePhaseData.forEachInstances.length > 0) {
2171
+ const forEachResult = await renderFastChangingDataForForEachInstances(
2172
+ instancePhaseData.forEachInstances,
2173
+ headlessComps,
2174
+ fastViewState
2175
+ );
2176
+ if (forEachResult) {
2177
+ const existingHeadless = fastViewState.__headlessInstances || {};
2178
+ fastViewState = {
2179
+ ...fastViewState,
2180
+ __headlessInstances: { ...existingHeadless, ...forEachResult }
2181
+ };
2182
+ }
2183
+ }
2184
+ }
1999
2185
  await sendResponse(
2000
2186
  vite,
2001
2187
  res,
2002
2188
  url,
2003
2189
  preRenderedPath,
2004
2190
  pageParts,
2005
- renderedFast.rendered,
2006
- renderedFast.carryForward,
2191
+ fastViewState,
2192
+ fastCarryForward,
2007
2193
  clientTrackByMap,
2008
2194
  projectInit,
2009
2195
  pluginsForPage,
2010
2196
  options,
2011
- renderedSlowly.rendered
2197
+ renderedSlowly.rendered,
2198
+ timing
2012
2199
  );
2013
2200
  }
2014
- async function handleDirectRequest(vite, route, options, slowlyPhase, pageParams, pageProps, allPluginClientInits, allPluginsWithInit, projectInit, res, url) {
2201
+ async function handleDirectRequest(vite, route, options, slowlyPhase, pageParams, pageProps, allPluginClientInits, allPluginsWithInit, projectInit, res, url, timing) {
2202
+ const loadStart = Date.now();
2015
2203
  const pagePartsResult = await loadPageParts(
2016
2204
  vite,
2017
2205
  route,
@@ -2019,9 +2207,11 @@ async function handleDirectRequest(vite, route, options, slowlyPhase, pageParams
2019
2207
  options.projectRootFolder,
2020
2208
  options.jayRollupConfig
2021
2209
  );
2210
+ timing?.recordViteSsr(Date.now() - loadStart);
2022
2211
  if (!pagePartsResult.val) {
2023
- console.log(pagePartsResult.validations.join("\n"));
2212
+ getLogger().info(pagePartsResult.validations.join("\n"));
2024
2213
  res.status(500).end(pagePartsResult.validations.join("\n"));
2214
+ timing?.end();
2025
2215
  return;
2026
2216
  }
2027
2217
  const {
@@ -2035,21 +2225,89 @@ async function handleDirectRequest(vite, route, options, slowlyPhase, pageParams
2035
2225
  allPluginsWithInit,
2036
2226
  usedPackages
2037
2227
  );
2228
+ const slowStart = Date.now();
2038
2229
  const renderedSlowly = await slowlyPhase.runSlowlyForPage(pageParams, pageProps, pageParts);
2039
2230
  if (renderedSlowly.kind !== "PhaseOutput") {
2231
+ timing?.recordSlowRender(Date.now() - slowStart);
2040
2232
  if (renderedSlowly.kind === "ClientError") {
2041
2233
  handleOtherResponseCodes(res, renderedSlowly);
2042
2234
  }
2235
+ timing?.end();
2043
2236
  return;
2044
2237
  }
2238
+ let instanceViewStates;
2239
+ let instancePhaseDataForFast;
2240
+ let forEachInstancesForFast;
2241
+ const headlessInstanceComponents = pagePartsResult.val.headlessInstanceComponents ?? [];
2242
+ if (headlessInstanceComponents.length > 0) {
2243
+ const jayHtmlContent = await fs$1.readFile(route.jayHtmlPath, "utf-8");
2244
+ const discoveryResult = discoverHeadlessInstances(jayHtmlContent);
2245
+ if (discoveryResult.forEachInstances.length > 0) {
2246
+ const validationErrors = validateForEachInstances(
2247
+ discoveryResult.forEachInstances,
2248
+ headlessInstanceComponents
2249
+ );
2250
+ if (validationErrors.length > 0) {
2251
+ getLogger().error(
2252
+ `[SlowRender] ForEach instance validation failed: ${validationErrors.join(", ")}`
2253
+ );
2254
+ res.status(500).end(validationErrors.join("\n"));
2255
+ timing?.end();
2256
+ return;
2257
+ }
2258
+ forEachInstancesForFast = discoveryResult.forEachInstances;
2259
+ }
2260
+ if (discoveryResult.instances.length > 0) {
2261
+ const slowResult = await slowRenderInstances(
2262
+ discoveryResult.instances,
2263
+ headlessInstanceComponents
2264
+ );
2265
+ if (slowResult) {
2266
+ instanceViewStates = { ...slowResult.slowViewStates };
2267
+ instancePhaseDataForFast = slowResult.instancePhaseData;
2268
+ }
2269
+ }
2270
+ }
2271
+ timing?.recordSlowRender(Date.now() - slowStart);
2272
+ const fastStart = Date.now();
2045
2273
  const renderedFast = await renderFastChangingData(
2046
2274
  pageParams,
2047
2275
  pageProps,
2048
2276
  renderedSlowly.carryForward,
2049
2277
  pageParts
2050
2278
  );
2279
+ if (instancePhaseDataForFast && instanceViewStates) {
2280
+ const instanceFastResult = await renderFastChangingDataForInstances(
2281
+ instancePhaseDataForFast,
2282
+ headlessInstanceComponents
2283
+ );
2284
+ if (instanceFastResult) {
2285
+ for (const [coordKey, fastVS] of Object.entries(instanceFastResult.viewStates)) {
2286
+ instanceViewStates[coordKey] = {
2287
+ ...instanceViewStates[coordKey] || {},
2288
+ ...fastVS
2289
+ };
2290
+ }
2291
+ }
2292
+ }
2293
+ if (forEachInstancesForFast && renderedFast.kind === "PhaseOutput") {
2294
+ const forEachResult = await renderFastChangingDataForForEachInstances(
2295
+ forEachInstancesForFast,
2296
+ headlessInstanceComponents,
2297
+ { ...renderedSlowly.rendered, ...renderedFast.rendered }
2298
+ );
2299
+ if (forEachResult) {
2300
+ if (!instanceViewStates)
2301
+ instanceViewStates = {};
2302
+ for (const [coordKey, fastVS] of Object.entries(forEachResult)) {
2303
+ instanceViewStates[coordKey] = fastVS;
2304
+ }
2305
+ }
2306
+ }
2307
+ timing?.recordFastRender(Date.now() - fastStart);
2051
2308
  if (renderedFast.kind !== "PhaseOutput") {
2052
2309
  handleOtherResponseCodes(res, renderedFast);
2310
+ timing?.end();
2053
2311
  return;
2054
2312
  }
2055
2313
  let viewState;
@@ -2062,6 +2320,12 @@ async function handleDirectRequest(vite, route, options, slowlyPhase, pageParams
2062
2320
  } else {
2063
2321
  viewState = { ...renderedSlowly.rendered, ...renderedFast.rendered };
2064
2322
  }
2323
+ if (instanceViewStates && Object.keys(instanceViewStates).length > 0) {
2324
+ viewState = {
2325
+ ...viewState,
2326
+ __headlessInstances: instanceViewStates
2327
+ };
2328
+ }
2065
2329
  await sendResponse(
2066
2330
  vite,
2067
2331
  res,
@@ -2073,10 +2337,12 @@ async function handleDirectRequest(vite, route, options, slowlyPhase, pageParams
2073
2337
  clientTrackByMap,
2074
2338
  projectInit,
2075
2339
  pluginsForPage,
2076
- options
2340
+ options,
2341
+ void 0,
2342
+ timing
2077
2343
  );
2078
2344
  }
2079
- async function sendResponse(vite, res, url, jayHtmlPath, pageParts, viewState, carryForward, clientTrackByMap, projectInit, pluginsForPage, options, slowViewState) {
2345
+ async function sendResponse(vite, res, url, jayHtmlPath, pageParts, viewState, carryForward, clientTrackByMap, projectInit, pluginsForPage, options, slowViewState, timing) {
2080
2346
  const pageHtml = generateClientScript(
2081
2347
  viewState,
2082
2348
  carryForward,
@@ -2091,10 +2357,19 @@ async function sendResponse(vite, res, url, jayHtmlPath, pageParts, viewState, c
2091
2357
  slowViewState
2092
2358
  }
2093
2359
  );
2360
+ if (options.buildFolder) {
2361
+ const pageName = !url || url === "/" ? "index" : url.replace(/^\//, "").replace(/\//g, "-");
2362
+ const clientScriptDir = path__default.join(options.buildFolder, "client-scripts");
2363
+ await fs$1.mkdir(clientScriptDir, { recursive: true });
2364
+ await fs$1.writeFile(path__default.join(clientScriptDir, `${pageName}.html`), pageHtml, "utf-8");
2365
+ }
2366
+ const viteStart = Date.now();
2094
2367
  const compiledPageHtml = await vite.transformIndexHtml(!!url ? url : "/", pageHtml);
2368
+ timing?.recordViteClient(Date.now() - viteStart);
2095
2369
  res.status(200).set({ "Content-Type": "text/html" }).send(compiledPageHtml);
2370
+ timing?.end();
2096
2371
  }
2097
- async function preRenderJayHtml(route, slowViewState) {
2372
+ async function preRenderJayHtml(route, slowViewState, headlessContracts, headlessInstanceComponents) {
2098
2373
  const jayHtmlContent = await fs$1.readFile(route.jayHtmlPath, "utf-8");
2099
2374
  const contractPath = route.jayHtmlPath.replace(".jay-html", ".jay-contract");
2100
2375
  let contract;
@@ -2104,15 +2379,14 @@ async function preRenderJayHtml(route, slowViewState) {
2104
2379
  if (parseResult.val) {
2105
2380
  contract = parseResult.val;
2106
2381
  } else if (parseResult.validations.length > 0) {
2107
- console.error(
2108
- `[SlowRender] Contract parse error for ${contractPath}:`,
2109
- parseResult.validations
2382
+ getLogger().error(
2383
+ `[SlowRender] Contract parse error for ${contractPath}: ${parseResult.validations.join(", ")}`
2110
2384
  );
2111
2385
  return void 0;
2112
2386
  }
2113
2387
  } catch (error) {
2114
2388
  if (error.code !== "ENOENT") {
2115
- console.error(`[SlowRender] Error reading contract ${contractPath}:`, error);
2389
+ getLogger().error(`[SlowRender] Error reading contract ${contractPath}: ${error}`);
2116
2390
  return void 0;
2117
2391
  }
2118
2392
  }
@@ -2120,20 +2394,156 @@ async function preRenderJayHtml(route, slowViewState) {
2120
2394
  jayHtmlContent,
2121
2395
  slowViewState,
2122
2396
  contract,
2123
- sourceDir: path__default.dirname(route.jayHtmlPath)
2397
+ headlessContracts,
2398
+ sourceDir: path__default.dirname(route.jayHtmlPath),
2399
+ importResolver: JAY_IMPORT_RESOLVER
2124
2400
  });
2125
- if (result.val) {
2126
- return result.val.preRenderedJayHtml;
2401
+ if (!result.val) {
2402
+ if (result.validations.length > 0) {
2403
+ getLogger().error(
2404
+ `[SlowRender] Transform failed for ${route.jayHtmlPath}: ${result.validations.join(", ")}`
2405
+ );
2406
+ }
2407
+ return void 0;
2408
+ }
2409
+ let preRenderedJayHtml = result.val.preRenderedJayHtml;
2410
+ let instancePhaseData;
2411
+ let forEachInstances;
2412
+ if (headlessInstanceComponents.length > 0) {
2413
+ const discoveryResult = discoverHeadlessInstances(preRenderedJayHtml);
2414
+ preRenderedJayHtml = discoveryResult.preRenderedJayHtml;
2415
+ if (discoveryResult.forEachInstances.length > 0) {
2416
+ const validationErrors = validateForEachInstances(
2417
+ discoveryResult.forEachInstances,
2418
+ headlessInstanceComponents
2419
+ );
2420
+ if (validationErrors.length > 0) {
2421
+ getLogger().error(
2422
+ `[SlowRender] ForEach instance validation failed: ${validationErrors.join(", ")}`
2423
+ );
2424
+ return void 0;
2425
+ }
2426
+ forEachInstances = discoveryResult.forEachInstances;
2427
+ }
2428
+ if (discoveryResult.instances.length > 0) {
2429
+ const slowResult = await slowRenderInstances(
2430
+ discoveryResult.instances,
2431
+ headlessInstanceComponents
2432
+ );
2433
+ if (slowResult) {
2434
+ instancePhaseData = slowResult.instancePhaseData;
2435
+ const pass2Result = resolveHeadlessInstances(
2436
+ preRenderedJayHtml,
2437
+ slowResult.resolvedData,
2438
+ JAY_IMPORT_RESOLVER
2439
+ );
2440
+ if (pass2Result.val) {
2441
+ preRenderedJayHtml = pass2Result.val;
2442
+ }
2443
+ if (pass2Result.validations.length > 0) {
2444
+ getLogger().error(
2445
+ `[SlowRender] Instance resolution warnings for ${route.jayHtmlPath}: ${pass2Result.validations.join(", ")}`
2446
+ );
2447
+ }
2448
+ }
2449
+ }
2450
+ }
2451
+ return { preRenderedJayHtml, instancePhaseData, forEachInstances };
2452
+ }
2453
+ async function renderFastChangingDataForInstances(instancePhaseData, headlessInstanceComponents) {
2454
+ const componentByContractName = /* @__PURE__ */ new Map();
2455
+ for (const comp of headlessInstanceComponents) {
2456
+ componentByContractName.set(comp.contractName, comp);
2127
2457
  }
2128
- if (result.validations.length > 0) {
2129
- console.error(
2130
- `[SlowRender] Transform failed for ${route.jayHtmlPath}:`,
2131
- result.validations
2458
+ const viewStates = {};
2459
+ const carryForwards = {};
2460
+ let hasResults = false;
2461
+ for (const instance of instancePhaseData.discovered) {
2462
+ const coordKey = instance.coordinate.join("/");
2463
+ const comp = componentByContractName.get(instance.contractName);
2464
+ if (!comp || !comp.compDefinition.fastRender) {
2465
+ continue;
2466
+ }
2467
+ const instanceCarryForward = instancePhaseData.carryForwards[coordKey] || {};
2468
+ const services = resolveServices(comp.compDefinition.services);
2469
+ const fastResult = await comp.compDefinition.fastRender(
2470
+ instance.props,
2471
+ instanceCarryForward,
2472
+ ...services
2132
2473
  );
2474
+ if (fastResult.kind === "PhaseOutput") {
2475
+ viewStates[coordKey] = fastResult.rendered;
2476
+ carryForwards[coordKey] = fastResult.carryForward;
2477
+ hasResults = true;
2478
+ }
2133
2479
  }
2134
- return void 0;
2480
+ return hasResults ? { viewStates, carryForwards } : void 0;
2481
+ }
2482
+ async function renderFastChangingDataForForEachInstances(forEachInstances, headlessInstanceComponents, mergedViewState) {
2483
+ const componentByContractName = /* @__PURE__ */ new Map();
2484
+ for (const comp of headlessInstanceComponents) {
2485
+ componentByContractName.set(comp.contractName, comp);
2486
+ }
2487
+ const viewStates = {};
2488
+ let hasResults = false;
2489
+ for (const instance of forEachInstances) {
2490
+ const comp = componentByContractName.get(instance.contractName);
2491
+ if (!comp)
2492
+ continue;
2493
+ const items = resolvePathValue(mergedViewState, instance.forEachPath);
2494
+ if (!Array.isArray(items))
2495
+ continue;
2496
+ for (const item of items) {
2497
+ const trackByValue = String(item[instance.trackBy]);
2498
+ const props = {};
2499
+ for (const [propName, binding] of Object.entries(instance.propBindings)) {
2500
+ props[propName] = resolveBinding(String(binding), item);
2501
+ }
2502
+ if (comp.compDefinition.fastRender) {
2503
+ const services = resolveServices(comp.compDefinition.services);
2504
+ const fastResult = await comp.compDefinition.fastRender(props, ...services);
2505
+ if (fastResult.kind === "PhaseOutput") {
2506
+ const coord = [trackByValue, instance.coordinateSuffix].toString();
2507
+ viewStates[coord] = fastResult.rendered;
2508
+ hasResults = true;
2509
+ }
2510
+ }
2511
+ }
2512
+ }
2513
+ return hasResults ? viewStates : void 0;
2135
2514
  }
2136
- async function mkDevServer(options) {
2515
+ function resolvePathValue(obj, path2) {
2516
+ return path2.split(".").reduce((current, segment) => current?.[segment], obj);
2517
+ }
2518
+ function resolveBinding(binding, item) {
2519
+ const match = binding.match(/^\{(.+)\}$/);
2520
+ if (match) {
2521
+ return String(item[match[1]] ?? "");
2522
+ }
2523
+ return binding;
2524
+ }
2525
+ async function materializeDynamicContracts(projectRootFolder, buildFolder, viteServer) {
2526
+ try {
2527
+ const services = getServiceRegistry();
2528
+ const result = await materializeContracts(
2529
+ {
2530
+ projectRoot: projectRootFolder,
2531
+ outputDir: path__default.join(buildFolder, "materialized-contracts"),
2532
+ verbose: false,
2533
+ viteServer
2534
+ },
2535
+ services
2536
+ );
2537
+ const dynamicCount = result.dynamicCount;
2538
+ if (dynamicCount > 0) {
2539
+ getLogger().info(`[Contracts] Materialized ${dynamicCount} dynamic contract(s)`);
2540
+ }
2541
+ } catch (error) {
2542
+ getLogger().warn(`[Contracts] Failed to materialize dynamic contracts: ${error.message}`);
2543
+ }
2544
+ }
2545
+ async function mkDevServer(rawOptions) {
2546
+ const options = defaults(rawOptions);
2137
2547
  const {
2138
2548
  publicBaseUrlPath,
2139
2549
  pagesRootFolder,
@@ -2141,23 +2551,20 @@ async function mkDevServer(options) {
2141
2551
  buildFolder,
2142
2552
  jayRollupConfig,
2143
2553
  dontCacheSlowly
2144
- } = defaults(options);
2554
+ } = options;
2555
+ const viteLogLevel = options.logLevel === "silent" ? "silent" : options.logLevel === "verbose" ? "info" : "warn";
2145
2556
  const lifecycleManager = new ServiceLifecycleManager(projectRootFolder);
2146
2557
  setupGracefulShutdown(lifecycleManager);
2147
- const vite = await createServer({
2148
- server: { middlewareMode: true },
2149
- plugins: [...jayStackCompiler(jayRollupConfig)],
2150
- appType: "custom",
2558
+ const vite = await createViteServer({
2559
+ projectRoot: projectRootFolder,
2560
+ pagesRoot: pagesRootFolder,
2151
2561
  base: publicBaseUrlPath,
2152
- root: pagesRootFolder,
2153
- ssr: {
2154
- // Mark stack-server-runtime as external so Vite uses Node's require
2155
- // This ensures lib/init.ts and dev-server share the same module instance
2156
- external: ["@jay-framework/stack-server-runtime"]
2157
- }
2562
+ jayRollupConfig,
2563
+ logLevel: viteLogLevel
2158
2564
  });
2159
2565
  lifecycleManager.setViteServer(vite);
2160
2566
  await lifecycleManager.initialize();
2567
+ await materializeDynamicContracts(projectRootFolder, buildFolder, vite);
2161
2568
  setupServiceHotReload(vite, lifecycleManager);
2162
2569
  setupActionRouter(vite);
2163
2570
  const routes = await initRoutes(pagesRootFolder);
@@ -2189,7 +2596,7 @@ async function mkDevServer(options) {
2189
2596
  }
2190
2597
  function setupGracefulShutdown(lifecycleManager) {
2191
2598
  const shutdown = async (signal) => {
2192
- console.log(`
2599
+ getLogger().important(`
2193
2600
  ${signal} received, shutting down gracefully...`);
2194
2601
  await lifecycleManager.shutdown();
2195
2602
  process.exit(0);
@@ -2205,7 +2612,7 @@ function setupServiceHotReload(vite, lifecycleManager) {
2205
2612
  vite.watcher.add(initFilePath);
2206
2613
  vite.watcher.on("change", async (changedPath) => {
2207
2614
  if (changedPath === initFilePath) {
2208
- console.log("[Services] lib/init.ts changed, reloading services...");
2615
+ getLogger().info("[Services] lib/init.ts changed, reloading services...");
2209
2616
  try {
2210
2617
  await lifecycleManager.reload();
2211
2618
  vite.ws.send({
@@ -2213,7 +2620,7 @@ function setupServiceHotReload(vite, lifecycleManager) {
2213
2620
  path: "*"
2214
2621
  });
2215
2622
  } catch (error) {
2216
- console.error("[Services] Failed to reload services:", error);
2623
+ getLogger().error(`[Services] Failed to reload services: ${error}`);
2217
2624
  }
2218
2625
  }
2219
2626
  });
@@ -2221,7 +2628,7 @@ function setupServiceHotReload(vite, lifecycleManager) {
2221
2628
  function setupActionRouter(vite) {
2222
2629
  vite.middlewares.use(actionBodyParser());
2223
2630
  vite.middlewares.use(ACTION_ENDPOINT_BASE, createActionRouter());
2224
- console.log(`[Actions] Action router mounted at ${ACTION_ENDPOINT_BASE}`);
2631
+ getLogger().info(`[Actions] Action router mounted at ${ACTION_ENDPOINT_BASE}`);
2225
2632
  }
2226
2633
  function setupSlowRenderCacheInvalidation(vite, cache, pagesRootFolder) {
2227
2634
  vite.watcher.on("change", (changedPath) => {
@@ -2230,7 +2637,7 @@ function setupSlowRenderCacheInvalidation(vite, cache, pagesRootFolder) {
2230
2637
  }
2231
2638
  if (changedPath.endsWith(".jay-html")) {
2232
2639
  cache.invalidate(changedPath).then(() => {
2233
- console.log(`[SlowRender] Cache invalidated for ${changedPath}`);
2640
+ getLogger().info(`[SlowRender] Cache invalidated for ${changedPath}`);
2234
2641
  });
2235
2642
  return;
2236
2643
  }
@@ -2238,14 +2645,18 @@ function setupSlowRenderCacheInvalidation(vite, cache, pagesRootFolder) {
2238
2645
  const dir = path__default.dirname(changedPath);
2239
2646
  const jayHtmlPath = path__default.join(dir, "page.jay-html");
2240
2647
  cache.invalidate(jayHtmlPath).then(() => {
2241
- console.log(`[SlowRender] Cache invalidated for ${jayHtmlPath} (page.ts changed)`);
2648
+ getLogger().info(
2649
+ `[SlowRender] Cache invalidated for ${jayHtmlPath} (page.ts changed)`
2650
+ );
2242
2651
  });
2243
2652
  return;
2244
2653
  }
2245
2654
  if (changedPath.endsWith(".jay-contract")) {
2246
2655
  const jayHtmlPath = changedPath.replace(".jay-contract", ".jay-html");
2247
2656
  cache.invalidate(jayHtmlPath).then(() => {
2248
- console.log(`[SlowRender] Cache invalidated for ${jayHtmlPath} (contract changed)`);
2657
+ getLogger().info(
2658
+ `[SlowRender] Cache invalidated for ${jayHtmlPath} (contract changed)`
2659
+ );
2249
2660
  });
2250
2661
  return;
2251
2662
  }
@@ -2255,5 +2666,7 @@ export {
2255
2666
  ACTION_ENDPOINT_BASE,
2256
2667
  actionBodyParser,
2257
2668
  createActionRouter,
2669
+ createViteForCli,
2670
+ createViteServer,
2258
2671
  mkDevServer
2259
2672
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/dev-server",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.js",
@@ -22,21 +22,22 @@
22
22
  "test:watch": "vitest"
23
23
  },
24
24
  "dependencies": {
25
- "@jay-framework/compiler-jay-stack": "^0.11.0",
26
- "@jay-framework/compiler-shared": "^0.11.0",
27
- "@jay-framework/component": "^0.11.0",
28
- "@jay-framework/fullstack-component": "^0.11.0",
29
- "@jay-framework/runtime": "^0.11.0",
30
- "@jay-framework/stack-client-runtime": "^0.11.0",
31
- "@jay-framework/stack-route-scanner": "^0.11.0",
32
- "@jay-framework/stack-server-runtime": "^0.11.0",
33
- "@jay-framework/view-state-merge": "^0.11.0",
25
+ "@jay-framework/compiler-jay-stack": "^0.13.0",
26
+ "@jay-framework/compiler-shared": "^0.13.0",
27
+ "@jay-framework/component": "^0.13.0",
28
+ "@jay-framework/fullstack-component": "^0.13.0",
29
+ "@jay-framework/logger": "^0.13.0",
30
+ "@jay-framework/runtime": "^0.13.0",
31
+ "@jay-framework/stack-client-runtime": "^0.13.0",
32
+ "@jay-framework/stack-route-scanner": "^0.13.0",
33
+ "@jay-framework/stack-server-runtime": "^0.13.0",
34
+ "@jay-framework/view-state-merge": "^0.13.0",
34
35
  "vite": "^5.0.11"
35
36
  },
36
37
  "devDependencies": {
37
- "@jay-framework/dev-environment": "^0.11.0",
38
- "@jay-framework/jay-cli": "^0.11.0",
39
- "@jay-framework/stack-client-runtime": "^0.11.0",
38
+ "@jay-framework/dev-environment": "^0.13.0",
39
+ "@jay-framework/jay-cli": "^0.13.0",
40
+ "@jay-framework/stack-client-runtime": "^0.13.0",
40
41
  "@types/express": "^5.0.2",
41
42
  "@types/node": "^22.15.21",
42
43
  "nodemon": "^3.0.3",