@jay-framework/dev-server 0.24.1 → 0.24.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +2 -1
  2. package/dist/index.js +2163 -253
  3. package/package.json +15 -14
package/dist/index.js CHANGED
@@ -7,31 +7,33 @@ import { createRequire } from "module";
7
7
  import "@jay-framework/compiler-shared";
8
8
  import * as path from "node:path";
9
9
  import path__default from "node:path";
10
- import { injectHeadfullFSTemplates, JAY_IMPORT_RESOLVER, parseContract, slowRenderTransform, assignCoordinatesToJayHtml, discoverHeadlessInstances } from "@jay-framework/compiler-jay-html";
10
+ import { parseJayFile, JAY_IMPORT_RESOLVER, injectHeadfullFSTemplates, discoverHeadlessInstances, assignCoordinatesToJayHtml, parseAction, generateServerElementFile, parseContract, slowRenderTransform } from "@jay-framework/compiler-jay-html";
11
11
  import { getLogger, getDevLogger } from "@jay-framework/logger";
12
12
  import { createRequire as createRequire$1 } from "node:module";
13
- import * as fsSync from "node:fs";
14
- import fsSync__default from "node:fs";
13
+ import * as fs from "node:fs";
14
+ import fs__default$1 from "node:fs";
15
15
  import { scanRoutes, createRoute, routeToExpressRoute } from "@jay-framework/stack-route-scanner";
16
- import { discoverPluginsWithInit, sortPluginsByDependencies, executePluginServerInits, runInitCallbacks, discoverAndRegisterActions, actionRegistry, discoverAllPluginActions, runShutdownCallbacks, clearLifecycleCallbacks, clearServiceRegistry, clearClientInitData, loadPageParts, runLoadParams, DevSlowlyChangingPhase, SlowRenderCache, preparePluginClientInits, registerService, getServiceRegistry, materializeContracts, scanPlugins, clearServerElementCache, parseCookies, generateFrozenPageHtml, renderFastChangingData, generateClientScript, getClientInitData, mergeHeadTags, generateSSRPageHtml, slowRenderInstances, normalizeAndResolveInstanceProps } from "@jay-framework/stack-server-runtime";
17
- import * as fs from "node:fs/promises";
16
+ import { actionRegistry, scanPlugins, serializeHeadTags, mergeHeadTags, discoverPluginsWithInit, sortPluginsByDependencies, executePluginServerInits, runInitCallbacks, runShutdownCallbacks, clearLifecycleCallbacks, clearServiceRegistry, clearClientInitData, runLoadParams, DevSlowlyChangingPhase, preparePluginClientInits, registerService, getServiceRegistry, parseCookies, renderFastChangingData, getClientInitData, slowRenderInstances, normalizeAndResolveInstanceProps } from "@jay-framework/stack-server-runtime";
17
+ import * as fs$1 from "node:fs/promises";
18
18
  import fs__default from "node:fs/promises";
19
+ import jsBeautify from "js-beautify";
20
+ import fs$2 from "fs";
21
+ import path$1 from "path";
22
+ import YAML from "yaml";
23
+ import crypto, { randomUUID } from "node:crypto";
24
+ import { isJayAction, isJayStreamAction, createJayService } from "@jay-framework/fullstack-component";
19
25
  import { pathToFileURL } from "node:url";
20
26
  import Busboy from "busboy";
21
- import fs$1 from "fs";
22
- import path$1 from "path";
23
- import crypto from "crypto";
24
- import { randomUUID } from "node:crypto";
25
- import { createJayService } from "@jay-framework/fullstack-component";
26
- const s$1 = createRequire(import.meta.url), e$1 = s$1("typescript"), c$1 = new Proxy(e$1, {
27
+ import crypto$1 from "crypto";
28
+ const s$2 = createRequire(import.meta.url), e$2 = s$2("typescript"), c$1 = new Proxy(e$2, {
27
29
  get(t, r) {
28
30
  return t[r];
29
31
  }
30
32
  });
31
- var __defProp2 = Object.defineProperty;
32
- var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
33
- var __publicField2 = (obj, key, value) => __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
34
- const s = createRequire(import.meta.url), e = s("typescript"), c = new Proxy(e, {
33
+ var __defProp$1 = Object.defineProperty;
34
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
35
+ var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
36
+ const s$1 = createRequire(import.meta.url), e$1 = s$1("typescript"), c = new Proxy(e$1, {
35
37
  get(t, r) {
36
38
  return t[r];
37
39
  }
@@ -180,7 +182,7 @@ function tsBindingNameToVariable(binding, accessedFrom, assignedFrom, propertyNa
180
182
  }
181
183
  class NameBindingResolver {
182
184
  constructor(parentNameResolver) {
183
- __publicField2(this, "variables", /* @__PURE__ */ new Map());
185
+ __publicField$1(this, "variables", /* @__PURE__ */ new Map());
184
186
  this.parentNameResolver = parentNameResolver;
185
187
  }
186
188
  addVariable(name, variable) {
@@ -455,7 +457,7 @@ class GlobalResolvedType {
455
457
  }
456
458
  class SourceFileBindingResolver {
457
459
  constructor(sourceFile) {
458
- __publicField2(this, "nameBindingResolvers", /* @__PURE__ */ new Map());
460
+ __publicField$1(this, "nameBindingResolvers", /* @__PURE__ */ new Map());
459
461
  this.nameBindingResolvers.set(sourceFile, new NameBindingResolver());
460
462
  const nbResolversQueue = [
461
463
  this.nameBindingResolvers.get(sourceFile)
@@ -1244,7 +1246,7 @@ function createImportChainTracker(options = {}) {
1244
1246
  }
1245
1247
  };
1246
1248
  }
1247
- const require$1 = createRequire$1(import.meta.url);
1249
+ const require$1$1 = createRequire$1(import.meta.url);
1248
1250
  function createDefaultPluginDetector() {
1249
1251
  const cache = /* @__PURE__ */ new Map();
1250
1252
  return {
@@ -1255,9 +1257,9 @@ function createDefaultPluginDetector() {
1255
1257
  }
1256
1258
  let result = false;
1257
1259
  try {
1258
- require$1.resolve(`${packageName}/plugin.yaml`, { paths: [projectRoot] });
1260
+ require$1$1.resolve(`${packageName}/plugin.yaml`, { paths: [projectRoot] });
1259
1261
  try {
1260
- require$1.resolve(`${packageName}/client`, { paths: [projectRoot] });
1262
+ require$1$1.resolve(`${packageName}/client`, { paths: [projectRoot] });
1261
1263
  result = true;
1262
1264
  } catch {
1263
1265
  result = false;
@@ -1428,16 +1430,16 @@ function jayStackCompiler(options = {}) {
1428
1430
  const importerDir = path.dirname(importer);
1429
1431
  let resolvedPath = path.resolve(importerDir, source);
1430
1432
  if (!resolvedPath.endsWith(".ts") && !resolvedPath.endsWith(".js")) {
1431
- if (fsSync.existsSync(resolvedPath + ".ts")) {
1433
+ if (fs.existsSync(resolvedPath + ".ts")) {
1432
1434
  resolvedPath += ".ts";
1433
- } else if (fsSync.existsSync(resolvedPath + ".js")) {
1435
+ } else if (fs.existsSync(resolvedPath + ".js")) {
1434
1436
  resolvedPath += ".js";
1435
1437
  } else {
1436
1438
  return null;
1437
1439
  }
1438
- } else if (resolvedPath.endsWith(".js") && !fsSync.existsSync(resolvedPath)) {
1440
+ } else if (resolvedPath.endsWith(".js") && !fs.existsSync(resolvedPath)) {
1439
1441
  const tsPath = resolvedPath.slice(0, -3) + ".ts";
1440
- if (fsSync.existsSync(tsPath)) {
1442
+ if (fs.existsSync(tsPath)) {
1441
1443
  resolvedPath = tsPath;
1442
1444
  } else {
1443
1445
  return null;
@@ -1452,7 +1454,7 @@ function jayStackCompiler(options = {}) {
1452
1454
  const actualPath = id.slice("\0jay-action:".length);
1453
1455
  let code;
1454
1456
  try {
1455
- code = await fsSync.promises.readFile(actualPath, "utf-8");
1457
+ code = await fs.promises.readFile(actualPath, "utf-8");
1456
1458
  } catch (err) {
1457
1459
  getLogger().error(
1458
1460
  `[action-transform] Could not read ${actualPath}: ${err}`
@@ -1610,224 +1612,2128 @@ async function createViteForCli(options) {
1610
1612
  });
1611
1613
  return vite;
1612
1614
  }
1613
- class ServiceLifecycleManager {
1614
- constructor(projectRoot, sourceBase = "src") {
1615
- /** Path to project's lib/init.ts (makeJayInit pattern) */
1616
- __publicField(this, "projectInitFilePath", null);
1617
- __publicField(this, "isInitialized", false);
1618
- __publicField(this, "viteServer", null);
1619
- __publicField(this, "pluginsWithInit", []);
1620
- this.projectRoot = projectRoot;
1621
- this.sourceBase = sourceBase;
1622
- }
1623
- /**
1624
- * Set the Vite server instance for SSR module loading
1625
- */
1626
- setViteServer(viteServer) {
1627
- this.viteServer = viteServer;
1628
- }
1629
- /**
1630
- * Finds the project init file using makeJayInit pattern.
1631
- * Looks in: {projectRoot}/{sourceBase}/init.{ts,js}
1632
- */
1633
- findProjectInitFile() {
1634
- const extensions = [".ts", ".js"];
1635
- for (const ext of extensions) {
1636
- const filePath = path.join(this.projectRoot, this.sourceBase, "init" + ext);
1637
- if (fsSync.existsSync(filePath)) {
1638
- return filePath;
1639
- }
1615
+ var __defProp2 = Object.defineProperty;
1616
+ var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1617
+ var __publicField2 = (obj, key, value) => __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
1618
+ const require$3 = createRequire(import.meta.url);
1619
+ async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfig, options) {
1620
+ const exists = await fs__default.access(route.compPath, fs__default.constants.F_OK).then(() => true).catch(() => false);
1621
+ const parts = [];
1622
+ const validations = [];
1623
+ if (exists) {
1624
+ const exportName = route.componentExport || "page";
1625
+ const pageComponent = (await vite.ssrLoadModule(route.compPath))[exportName];
1626
+ if (!pageComponent) {
1627
+ validations.push(
1628
+ `${route.compPath} does not export "${exportName}". The file exists but the expected export is missing — remove the file or add the export.`
1629
+ );
1630
+ } else {
1631
+ const clientImportPath = route.componentExport ? route.compPath.replace(/index\.js$/, "index.client.js") : route.compPath;
1632
+ parts.push({
1633
+ compDefinition: pageComponent,
1634
+ clientImport: `import {${exportName}} from '${clientImportPath}'`,
1635
+ clientPart: `{comp: ${exportName}.comp, contextMarkers: ${exportName}.contexts || []}`
1636
+ });
1640
1637
  }
1641
- return null;
1642
1638
  }
1643
- /**
1644
- * Initializes services by:
1645
- * 1. Discovering and executing plugin server inits (in dependency order)
1646
- * 2. Loading and executing project lib/init.ts
1647
- * 3. Running all registered onInit callbacks
1648
- * 4. Auto-discovering and registering actions
1649
- */
1650
- async initialize() {
1651
- const log = getLogger();
1652
- if (this.isInitialized) {
1653
- log.warn("[Services] Already initialized, skipping...");
1654
- return;
1639
+ const jayHtmlFilePath = options?.preRenderedPath ?? route.jayHtmlPath;
1640
+ const jayHtmlSource = options?.preRenderedContent ?? (await fs__default.readFile(jayHtmlFilePath)).toString();
1641
+ const fileName = path__default.basename(route.jayHtmlPath);
1642
+ const dirName = path__default.dirname(route.jayHtmlPath);
1643
+ const jayHtmlWithValidations = await parseJayFile(
1644
+ jayHtmlSource,
1645
+ fileName,
1646
+ dirName,
1647
+ {
1648
+ relativePath: jayRollupConfig.tsConfigFilePath
1649
+ },
1650
+ JAY_IMPORT_RESOLVER,
1651
+ projectBase
1652
+ );
1653
+ jayHtmlWithValidations.validations.push(...validations);
1654
+ return jayHtmlWithValidations.mapAsync(async (jayHtml) => {
1655
+ const usedPackages = /* @__PURE__ */ new Set();
1656
+ const headlessInstanceComponents = [];
1657
+ for await (const headlessImport of jayHtml.headlessImports) {
1658
+ const module = headlessImport.codeLink.module;
1659
+ const name = headlessImport.codeLink.names[0].name;
1660
+ const isLocalModule = module[0] === "." || module[0] === "/";
1661
+ let compDefinition;
1662
+ let clientModuleImport;
1663
+ let isNpmPackage;
1664
+ if (headlessImport.structural) {
1665
+ continue;
1666
+ } else {
1667
+ const modulePath = isLocalModule ? path__default.resolve(dirName, module) : require$3.resolve(module, { paths: require$3.resolve.paths(dirName) });
1668
+ compDefinition = (await vite.ssrLoadModule(modulePath))[name];
1669
+ const moduleImport = isLocalModule ? path__default.resolve(dirName, module) : module;
1670
+ isNpmPackage = !isLocalModule;
1671
+ clientModuleImport = isNpmPackage ? `${moduleImport}/client` : `${moduleImport}`;
1672
+ }
1673
+ if (isNpmPackage) {
1674
+ const packageName = module.startsWith("@") ? module.split("/").slice(0, 2).join("/") : module.split("/")[0];
1675
+ usedPackages.add(packageName);
1676
+ }
1677
+ if (headlessImport.key) {
1678
+ const key = headlessImport.key;
1679
+ const hasClientComp = !!compDefinition?.fastRender || !!compDefinition?.hasInteractive;
1680
+ const part = {
1681
+ key,
1682
+ compDefinition,
1683
+ clientImport: hasClientComp ? `import {${name}} from '${clientModuleImport}'` : "",
1684
+ clientPart: hasClientComp ? `{comp: ${name}.comp, contextMarkers: ${name}.contexts || [], key: '${key}'}` : "",
1685
+ contractInfo: headlessImport.contract ? {
1686
+ contractName: headlessImport.contract.name,
1687
+ metadata: headlessImport.metadata
1688
+ } : void 0,
1689
+ headlessProps: headlessImport.headlessProps
1690
+ };
1691
+ parts.push(part);
1692
+ }
1693
+ if (!headlessImport.key && headlessImport.contract) {
1694
+ headlessInstanceComponents.push({
1695
+ contractName: headlessImport.contractName,
1696
+ compDefinition,
1697
+ contract: headlessImport.contract
1698
+ });
1699
+ }
1655
1700
  }
1656
- this.projectInitFilePath = this.findProjectInitFile();
1657
- const discoveredPlugins = await discoverPluginsWithInit({
1658
- projectRoot: this.projectRoot,
1659
- verbose: true
1660
- });
1661
- this.pluginsWithInit = sortPluginsByDependencies(discoveredPlugins);
1662
- if (this.pluginsWithInit.length > 0) {
1663
- log.info(
1664
- `[Services] Found ${this.pluginsWithInit.length} plugin(s) with init: ${this.pluginsWithInit.map((p) => p.name).join(", ")}`
1701
+ const headlessContracts = jayHtml.headlessImports.filter((hi) => hi.contract !== void 0 && hi.key !== void 0).map((hi) => ({
1702
+ key: hi.key,
1703
+ contract: hi.contract,
1704
+ contractPath: hi.contractPath
1705
+ }));
1706
+ const jayHtmlForDiscovery = injectHeadfullFSTemplates(
1707
+ jayHtmlSource,
1708
+ dirName,
1709
+ JAY_IMPORT_RESOLVER
1710
+ );
1711
+ let discoveryResult;
1712
+ if (headlessInstanceComponents.length > 0) {
1713
+ const firstDiscovery = discoverHeadlessInstances(jayHtmlForDiscovery);
1714
+ const headlessContractNameSet = new Set(
1715
+ jayHtml.headlessImports.map((hi) => hi.contractName)
1665
1716
  );
1666
- }
1667
- await executePluginServerInits(this.pluginsWithInit, this.viteServer ?? void 0, true);
1668
- if (this.projectInitFilePath) {
1669
- log.info("[DevServer] Loading project init: src/init.ts");
1670
- try {
1671
- if (this.viteServer) {
1672
- const module = await this.viteServer.ssrLoadModule(this.projectInitFilePath);
1673
- if (module.init?._serverInit) {
1674
- log.info("[DevServer] Running server init: project");
1675
- const { setClientInitData } = await import("@jay-framework/stack-server-runtime");
1676
- const clientData = await module.init._serverInit();
1677
- if (clientData !== void 0 && clientData !== null) {
1678
- setClientInitData("project", clientData);
1679
- }
1680
- }
1681
- } else {
1682
- const fileUrl = pathToFileURL(this.projectInitFilePath).href;
1683
- await import(fileUrl);
1684
- }
1685
- } catch (error) {
1686
- log.error(`[Services] Failed to load project init: ${error}`);
1687
- throw error;
1688
- }
1717
+ const jayHtmlWithCoords = assignCoordinatesToJayHtml(
1718
+ firstDiscovery.preRenderedJayHtml,
1719
+ headlessContractNameSet
1720
+ );
1721
+ discoveryResult = discoverHeadlessInstances(jayHtmlWithCoords);
1689
1722
  } else {
1690
- log.info("[Services] No init.ts found, skipping project initialization");
1723
+ discoveryResult = {
1724
+ instances: [],
1725
+ forEachInstances: [],
1726
+ preRenderedJayHtml: jayHtmlSource
1727
+ };
1691
1728
  }
1692
- await runInitCallbacks();
1693
- log.info("[Services] Initialization complete");
1694
- await this.discoverActions();
1695
- this.isInitialized = true;
1729
+ return {
1730
+ parts,
1731
+ serverTrackByMap: jayHtml.serverTrackByMap,
1732
+ clientTrackByMap: jayHtml.clientTrackByMap,
1733
+ usedPackages,
1734
+ headlessContracts,
1735
+ headlessInstanceComponents,
1736
+ discoveredInstances: discoveryResult.instances,
1737
+ forEachInstances: discoveryResult.forEachInstances,
1738
+ linkedCssFiles: jayHtml.linkedCssFiles ?? [],
1739
+ linkedComponentFiles: jayHtml.linkedComponentFiles ?? [],
1740
+ scripts: jayHtml.scripts
1741
+ };
1742
+ });
1743
+ }
1744
+ var __defProp22 = Object.defineProperty;
1745
+ var __defNormalProp22 = (obj, key, value) => key in obj ? __defProp22(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1746
+ var __publicField22 = (obj, key, value) => __defNormalProp22(obj, key + "", value);
1747
+ class JayAtomicType {
1748
+ constructor(name) {
1749
+ __publicField22(this, "kind", 0);
1750
+ this.name = name;
1696
1751
  }
1697
- /**
1698
- * Auto-discovers and registers actions from project and plugins.
1699
- */
1700
- async discoverActions() {
1701
- const log = getLogger();
1702
- let totalActions = 0;
1703
- try {
1704
- const result = await discoverAndRegisterActions({
1705
- projectRoot: this.projectRoot,
1706
- actionsDir: path.join(this.sourceBase, "actions"),
1707
- registry: actionRegistry,
1708
- verbose: true,
1709
- viteServer: this.viteServer ?? void 0
1710
- });
1711
- totalActions += result.actionCount;
1712
- } catch (error) {
1713
- log.error(`[Actions] Failed to auto-discover project actions: ${error}`);
1752
+ }
1753
+ new JayAtomicType("string");
1754
+ new JayAtomicType("string");
1755
+ new JayAtomicType("number");
1756
+ new JayAtomicType("boolean");
1757
+ new JayAtomicType("Date");
1758
+ new JayAtomicType("file");
1759
+ new JayAtomicType("Unknown");
1760
+ class JayObjectType {
1761
+ constructor(name, props) {
1762
+ __publicField22(this, "kind", 8);
1763
+ this.name = name;
1764
+ this.props = props;
1765
+ }
1766
+ }
1767
+ new JayObjectType("Error", {
1768
+ message: new JayAtomicType("string"),
1769
+ name: new JayAtomicType("string"),
1770
+ stack: new JayAtomicType("string")
1771
+ });
1772
+ function isOptionalType(aType) {
1773
+ return aType.kind === 14;
1774
+ }
1775
+ function isAtomicType(aType) {
1776
+ return aType.kind === 0;
1777
+ }
1778
+ function isEnumType(aType) {
1779
+ return aType.kind === 2;
1780
+ }
1781
+ function isImportedType(aType) {
1782
+ return aType.kind === 4;
1783
+ }
1784
+ function isObjectType(aType) {
1785
+ return aType.kind === 8;
1786
+ }
1787
+ function isArrayType(aType) {
1788
+ return aType.kind === 9;
1789
+ }
1790
+ function isRecordType(aType) {
1791
+ return aType.kind === 11;
1792
+ }
1793
+ function jayTypeToJsonSchema(type) {
1794
+ if (isOptionalType(type)) {
1795
+ return jayTypeToJsonSchema(type.innerType);
1796
+ }
1797
+ if (isAtomicType(type)) {
1798
+ const name = type.name.toLowerCase();
1799
+ if (name === "string" || name === "number" || name === "boolean") {
1800
+ return { type: name };
1714
1801
  }
1715
- try {
1716
- const pluginActions = await discoverAllPluginActions({
1717
- projectRoot: this.projectRoot,
1718
- registry: actionRegistry,
1719
- verbose: true,
1720
- viteServer: this.viteServer ?? void 0
1721
- });
1722
- totalActions += pluginActions.length;
1723
- } catch (error) {
1724
- log.error(`[Actions] Failed to auto-discover plugin actions: ${error}`);
1802
+ if (name === "file") {
1803
+ return { type: "string", description: "Binary file upload (JayFile)" };
1725
1804
  }
1726
- if (totalActions > 0) {
1727
- log.info(`[Actions] Auto-registered ${totalActions} action(s) total`);
1805
+ return { type: "string" };
1806
+ }
1807
+ if (isEnumType(type)) {
1808
+ return { type: "string", enum: type.values };
1809
+ }
1810
+ if (isImportedType(type)) {
1811
+ return { type: "object", description: `Contract: ${type.name}` };
1812
+ }
1813
+ if (isArrayType(type)) {
1814
+ const itemSchema = jayTypeToJsonSchema(type.itemType);
1815
+ if (itemSchema) {
1816
+ return { type: "array", items: itemSchema };
1728
1817
  }
1818
+ return { type: "array" };
1729
1819
  }
1730
- /**
1731
- * Shuts down services gracefully with timeout
1732
- */
1733
- async shutdown(timeoutMs = 5e3) {
1734
- const log = getLogger();
1735
- if (!this.isInitialized) {
1736
- return;
1820
+ if (isRecordType(type)) {
1821
+ const valueSchema = jayTypeToJsonSchema(type.itemType);
1822
+ if (valueSchema) {
1823
+ return { type: "object", additionalProperties: valueSchema };
1737
1824
  }
1738
- log.info("[Services] Shutting down...");
1739
- try {
1740
- await Promise.race([
1741
- runShutdownCallbacks(),
1742
- new Promise(
1743
- (_, reject) => setTimeout(() => reject(new Error("Shutdown timeout")), timeoutMs)
1744
- )
1745
- ]);
1746
- log.info("[Services] Shutdown complete");
1747
- } catch (error) {
1748
- if (error.message === "Shutdown timeout") {
1749
- log.warn(`[Services] Shutdown timed out after ${timeoutMs}ms`);
1750
- } else {
1751
- log.error(`[Services] Shutdown error: ${error}`);
1825
+ return { type: "object" };
1826
+ }
1827
+ if (isObjectType(type)) {
1828
+ const properties = {};
1829
+ const required = [];
1830
+ for (const [key, propType] of Object.entries(type.props)) {
1831
+ const isOpt = isOptionalType(propType);
1832
+ const schema = jayTypeToJsonSchema(propType);
1833
+ if (schema) {
1834
+ properties[key] = schema;
1835
+ if (!isOpt) {
1836
+ required.push(key);
1837
+ }
1752
1838
  }
1753
- } finally {
1754
- this.isInitialized = false;
1755
1839
  }
1840
+ return {
1841
+ type: "object",
1842
+ properties,
1843
+ ...required.length > 0 && { required }
1844
+ };
1756
1845
  }
1757
- /**
1758
- * Hot reload: shutdown, clear caches, re-import, and re-initialize
1759
- */
1760
- async reload() {
1761
- const log = getLogger();
1762
- log.info("[Services] Reloading services...");
1763
- await this.shutdown();
1764
- clearLifecycleCallbacks();
1765
- clearServiceRegistry();
1766
- clearClientInitData();
1767
- actionRegistry.clear();
1768
- if (this.projectInitFilePath && this.viteServer) {
1769
- const moduleNode = this.viteServer.moduleGraph.getModuleById(this.projectInitFilePath);
1770
- if (moduleNode) {
1771
- await this.viteServer.moduleGraph.invalidateModule(moduleNode);
1772
- }
1773
- } else if (this.projectInitFilePath) {
1774
- delete require.cache[require.resolve(this.projectInitFilePath)];
1775
- }
1776
- this.isInitialized = false;
1777
- await this.initialize();
1778
- log.info("[Services] Reload complete");
1846
+ return null;
1847
+ }
1848
+ var RuntimeMode = /* @__PURE__ */ ((RuntimeMode2) => {
1849
+ RuntimeMode2["MainTrusted"] = "mainTrusted";
1850
+ RuntimeMode2["MainSandbox"] = "mainSandbox";
1851
+ RuntimeMode2["WorkerTrusted"] = "workerTrusted";
1852
+ RuntimeMode2["WorkerSandbox"] = "workerSandbox";
1853
+ return RuntimeMode2;
1854
+ })(RuntimeMode || {});
1855
+ const TS_EXTENSION = ".ts";
1856
+ const JAY_QUERY_PREFIX = "?jay-";
1857
+ const JAY_QUERY_HYDRATE = `${JAY_QUERY_PREFIX}hydrate`;
1858
+ [
1859
+ // Hydrate target
1860
+ {
1861
+ pattern: JAY_QUERY_HYDRATE,
1862
+ buildEnv: "client",
1863
+ isHydrate: true
1864
+ },
1865
+ // Build environments
1866
+ {
1867
+ pattern: `${JAY_QUERY_PREFIX}${"client"}`,
1868
+ buildEnv: "client"
1869
+ /* Client */
1870
+ },
1871
+ {
1872
+ pattern: `${JAY_QUERY_PREFIX}${"server"}`,
1873
+ buildEnv: "server"
1874
+ /* Server */
1875
+ },
1876
+ // Runtime modes (with .ts suffix)
1877
+ {
1878
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.MainSandbox}${TS_EXTENSION}`,
1879
+ runtimeMode: RuntimeMode.MainSandbox
1880
+ },
1881
+ {
1882
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerTrusted}${TS_EXTENSION}`,
1883
+ runtimeMode: RuntimeMode.WorkerTrusted
1884
+ },
1885
+ {
1886
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerSandbox}${TS_EXTENSION}`,
1887
+ runtimeMode: RuntimeMode.WorkerSandbox
1888
+ },
1889
+ // Runtime modes (without .ts suffix)
1890
+ {
1891
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.MainSandbox}`,
1892
+ runtimeMode: RuntimeMode.MainSandbox
1893
+ },
1894
+ {
1895
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerTrusted}`,
1896
+ runtimeMode: RuntimeMode.WorkerTrusted
1897
+ },
1898
+ {
1899
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerSandbox}`,
1900
+ runtimeMode: RuntimeMode.WorkerSandbox
1779
1901
  }
1780
- /**
1781
- * Returns the path to the init file if found.
1782
- */
1783
- getInitFilePath() {
1784
- return this.projectInitFilePath;
1902
+ ];
1903
+ function checkValidationErrors(withValidations) {
1904
+ const { validations } = withValidations;
1905
+ if (validations.length > 0) {
1906
+ throw new Error(validations.join("\n"));
1785
1907
  }
1786
- /**
1787
- * Returns project init info for client-side embedding.
1788
- */
1789
- getProjectInit() {
1790
- if (!this.projectInitFilePath) {
1791
- return null;
1792
- }
1793
- return {
1794
- importPath: this.projectInitFilePath,
1795
- initExport: "init"
1796
- };
1908
+ return withValidations.val;
1909
+ }
1910
+ const { html: htmlBeautify } = jsBeautify;
1911
+ createRequire(import.meta.url);
1912
+ function normalizeActionEntry(entry) {
1913
+ if (typeof entry === "string") {
1914
+ return { name: entry };
1797
1915
  }
1798
- /**
1799
- * Returns the discovered plugins with init configurations.
1800
- * Sorted by dependencies (plugins with no deps first).
1801
- */
1802
- getPluginsWithInit() {
1803
- return this.pluginsWithInit;
1916
+ return { name: entry.name, action: entry.action, devOnly: entry.devOnly };
1917
+ }
1918
+ function loadPluginManifest(pluginDir) {
1919
+ const pluginYamlPath = path$1.join(pluginDir, "plugin.yaml");
1920
+ if (!fs$2.existsSync(pluginYamlPath)) {
1921
+ return null;
1804
1922
  }
1805
- /**
1806
- * Checks if services are initialized
1807
- */
1808
- isReady() {
1809
- return this.isInitialized;
1923
+ try {
1924
+ const yamlContent = fs$2.readFileSync(pluginYamlPath, "utf-8");
1925
+ return YAML.parse(yamlContent);
1926
+ } catch (error) {
1927
+ return null;
1810
1928
  }
1811
1929
  }
1812
- function deepMergeViewStates(base, overlay, trackByMap, path2 = "") {
1813
- if (!base && !overlay) return {};
1814
- if (!base) return overlay || {};
1815
- if (!overlay) return base || {};
1816
- const result = {};
1817
- const allKeys = /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(overlay)]);
1818
- for (const key of allKeys) {
1819
- const baseValue = base[key];
1820
- const overlayValue = overlay[key];
1821
- const currentPath = path2 ? `${path2}.${key}` : key;
1822
- if (overlayValue === void 0) {
1823
- result[key] = baseValue;
1824
- } else if (baseValue === void 0) {
1825
- result[key] = overlayValue;
1826
- } else if (Array.isArray(baseValue) && Array.isArray(overlayValue)) {
1827
- const trackByField = trackByMap[currentPath];
1828
- if (trackByField) {
1829
- result[key] = mergeArraysByTrackBy(
1830
- baseValue,
1930
+ const s = createRequire(import.meta.url), e = s("typescript");
1931
+ new Proxy(e, {
1932
+ get(t, r) {
1933
+ return t[r];
1934
+ }
1935
+ });
1936
+ function asyncSwapScript(id, html) {
1937
+ const escapedHtml = html.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
1938
+ return `<script>(function(){var t=document.querySelector('[jay-async="${id}:pending"]');if(t){var d=document.createElement('div');d.innerHTML='${escapedHtml}';t.replaceWith(d.firstChild);}window.__jay&&window.__jay.hydrateAsync&&window.__jay.hydrateAsync('${id}');})()<\/script>`;
1939
+ }
1940
+ function generatePromiseReconstruction(outcomes) {
1941
+ if (outcomes.length === 0) return "";
1942
+ return outcomes.map((outcome) => {
1943
+ if (outcome.status === "resolved") {
1944
+ return ` viewState[${JSON.stringify(outcome.id)}] = Promise.resolve(${JSON.stringify(outcome.value)});`;
1945
+ } else {
1946
+ const errMsg = outcome.value?.message ?? "Unknown error";
1947
+ return ` viewState[${JSON.stringify(outcome.id)}] = Promise.reject(new Error(${JSON.stringify(errMsg)}));`;
1948
+ }
1949
+ }).join("\n") + "\n";
1950
+ }
1951
+ function buildScriptFragments(parts, clientInitData, projectInit, pluginInits, options) {
1952
+ const { enableAutomation = true, slowViewState } = options;
1953
+ const hasSlowViewState = slowViewState && Object.keys(slowViewState).length > 0;
1954
+ const clientParts = parts.filter((part) => part.clientPart);
1955
+ const partImports = clientParts.length > 0 ? clientParts.map((part) => part.clientImport).join("\n") + "\n" : "";
1956
+ const compositeParts = clientParts.length > 0 ? `[
1957
+ ${clientParts.map((part) => " " + part.clientPart).join(",\n")}
1958
+ ]` : "[]";
1959
+ const hasClientInit = projectInit || pluginInits.length > 0;
1960
+ const pluginClientInitImports = pluginInits.map((plugin, idx) => {
1961
+ return `import { ${plugin.initExport} as jayInit${idx} } from "${plugin.importPath}";`;
1962
+ }).join("\n ");
1963
+ const projectInitImport = projectInit ? `import { ${projectInit.initExport || "init"} as projectJayInit } from "${projectInit.importPath}";` : "";
1964
+ const pluginClientInitCalls = pluginInits.map((plugin, idx) => {
1965
+ const pluginData = clientInitData[plugin.name] || {};
1966
+ return `if (typeof jayInit${idx}._clientInit === 'function') {
1967
+ console.log('[DevServer] Running client init: ${plugin.name}');
1968
+ await jayInit${idx}._clientInit(${JSON.stringify(pluginData)});
1969
+ }`;
1970
+ }).join("\n ");
1971
+ const projectInitCall = projectInit ? `if (typeof projectJayInit._clientInit === 'function') {
1972
+ console.log('[DevServer] Running client init: project');
1973
+ const projectData = ${JSON.stringify(clientInitData["project"] || {})};
1974
+ await projectJayInit._clientInit(projectData);
1975
+ }` : "";
1976
+ const clientInitExecution = hasClientInit ? `
1977
+ // Plugin client initialization (in dependency order)
1978
+ ${pluginClientInitCalls}
1979
+
1980
+ // Project client initialization
1981
+ ${projectInitCall}
1982
+ ` : "";
1983
+ const automationImport = enableAutomation ? hasSlowViewState ? `import { wrapWithAutomation, AUTOMATION_CONTEXT } from "@jay-framework/runtime-automation";
1984
+ import { registerGlobalContext } from "@jay-framework/runtime";
1985
+ import { deepMergeViewStates } from "@jay-framework/view-state-merge";` : `import { wrapWithAutomation, AUTOMATION_CONTEXT } from "@jay-framework/runtime-automation";
1986
+ import { registerGlobalContext } from "@jay-framework/runtime";` : "";
1987
+ const slowViewStateDecl = enableAutomation && hasSlowViewState ? `const slowViewState = ${JSON.stringify(slowViewState)};` : "";
1988
+ return {
1989
+ partImports,
1990
+ compositeParts,
1991
+ pluginClientInitImports,
1992
+ projectInitImport,
1993
+ clientInitExecution,
1994
+ automationImport,
1995
+ slowViewStateDecl
1996
+ };
1997
+ }
1998
+ function buildPageReloadHmrScript() {
1999
+ return `
2000
+ if (import.meta.hot) {
2001
+ import.meta.hot.on('jay:page-reload', (data) => {
2002
+ const prefix = data.routePrefix;
2003
+ const pathname = window.location.pathname;
2004
+ if (pathname === prefix || pathname.startsWith(prefix + '/')) {
2005
+ window.location.reload();
2006
+ }
2007
+ });
2008
+ }`;
2009
+ }
2010
+ function buildFreezeScript(routePattern) {
2011
+ const routePatternLiteral = routePattern ? `'${routePattern}'` : "undefined";
2012
+ return `
2013
+ // Page Freeze (DL#127, DL#128 iframe addendum)
2014
+ // Sticky embed mode: URL param sets a session cookie so in-iframe navigation preserves it
2015
+ if (new URLSearchParams(window.location.search).has('_jay_embed')) {
2016
+ document.cookie = '_jay_embed=1;path=/;samesite=lax';
2017
+ }
2018
+ const __jayEmbedMode = document.cookie.split(';').some(c => c.trim().startsWith('_jay_embed='));
2019
+
2020
+ async function __jayDoFreeze() {
2021
+ const automation = window.__jay?.automation;
2022
+ if (!automation) return;
2023
+
2024
+ // Visual feedback: white flash
2025
+ const flash = document.createElement('div');
2026
+ flash.style.cssText = 'position:fixed;inset:0;background:white;z-index:999999;opacity:0.8;pointer-events:none;transition:opacity 0.3s';
2027
+ document.body.appendChild(flash);
2028
+ requestAnimationFrame(() => { flash.style.opacity = '0'; });
2029
+ setTimeout(() => flash.remove(), 400);
2030
+
2031
+ // Audio feedback: camera shutter
2032
+ try {
2033
+ const ctx = new AudioContext();
2034
+ const buf = ctx.createBuffer(1, ctx.sampleRate * 0.15, ctx.sampleRate);
2035
+ const data = buf.getChannelData(0);
2036
+ for (let i = 0; i < data.length; i++) {
2037
+ data[i] = (Math.random() * 2 - 1) * Math.exp(-i / (ctx.sampleRate * 0.02));
2038
+ }
2039
+ const src = ctx.createBufferSource();
2040
+ src.buffer = buf;
2041
+ src.connect(ctx.destination);
2042
+ src.start();
2043
+ } catch {}
2044
+
2045
+ // Capture and save
2046
+ try {
2047
+ const state = automation.getPageState();
2048
+ const route = window.location.pathname;
2049
+ const routePattern = ${routePatternLiteral};
2050
+ const resp = await fetch('/_jay/freeze', {
2051
+ method: 'POST',
2052
+ headers: { 'Content-Type': 'application/json' },
2053
+ body: JSON.stringify({ route, routePattern, viewState: state.viewState }),
2054
+ });
2055
+ const { id } = await resp.json();
2056
+ if (__jayEmbedMode) {
2057
+ window.parent.postMessage({ type: 'jay:freeze', id, route }, '*');
2058
+ } else {
2059
+ window.open(route + '?_jay_freeze=' + id, '_blank');
2060
+ }
2061
+ } catch (err) {
2062
+ console.error('[Freeze] Failed:', err);
2063
+ }
2064
+ }
2065
+
2066
+ if (__jayEmbedMode) {
2067
+ // Notify parent of the current route on load (DL#128 route addendum)
2068
+ window.parent.postMessage({ type: 'jay:route', route: window.location.pathname, routePattern: ${routePatternLiteral} }, '*');
2069
+
2070
+ // Embed mode: parent triggers freeze via postMessage
2071
+ window.addEventListener('message', (e) => {
2072
+ if (e.data?.type === 'jay:requestFreeze') __jayDoFreeze();
2073
+ });
2074
+ } else {
2075
+ // Standalone: Alt+S / Option+S keyboard shortcut
2076
+ document.addEventListener('keydown', (e) => {
2077
+ if (e.altKey && e.code === 'KeyS') {
2078
+ e.preventDefault();
2079
+ __jayDoFreeze();
2080
+ }
2081
+ });
2082
+ }
2083
+
2084
+ // Targeted page reload: only reload if this page matches the changed route prefix
2085
+ ${buildPageReloadHmrScript()}`;
2086
+ }
2087
+ function buildAutomationWrap(options, mode) {
2088
+ const { enableAutomation = true, slowViewState } = options;
2089
+ const hasSlowViewState = slowViewState && Object.keys(slowViewState).length > 0;
2090
+ const appendDom = mode === "client";
2091
+ if (!enableAutomation) {
2092
+ return appendDom ? `
2093
+ target.appendChild(instance.element.dom);` : "";
2094
+ }
2095
+ const appendLine = appendDom ? `
2096
+ target.appendChild(wrapped.element.dom);` : "";
2097
+ const freezeScript = buildFreezeScript(options.routePattern);
2098
+ if (hasSlowViewState) {
2099
+ return `
2100
+ // Wrap with automation for dev tooling
2101
+ // Deep merge slow+fast ViewState so automation can see full page state
2102
+ const fullViewState = deepMergeViewStates(slowViewState, {...viewState, ...fastCarryForward}, trackByMap);
2103
+ const wrapped = wrapWithAutomation(instance, { initialViewState: fullViewState, trackByMap });
2104
+ registerGlobalContext(AUTOMATION_CONTEXT, wrapped.automation);
2105
+ window.__jay = window.__jay || {};
2106
+ window.__jay.automation = wrapped.automation;
2107
+ window.dispatchEvent(new Event('jay:automation-ready'));${appendLine}
2108
+ ${freezeScript}`;
2109
+ }
2110
+ return `
2111
+ // Wrap with automation for dev tooling
2112
+ const wrapped = wrapWithAutomation(instance);
2113
+ registerGlobalContext(AUTOMATION_CONTEXT, wrapped.automation);
2114
+ window.__jay = window.__jay || {};
2115
+ window.__jay.automation = wrapped.automation;
2116
+ window.dispatchEvent(new Event('jay:automation-ready'));${appendLine}
2117
+ ${freezeScript}`;
2118
+ }
2119
+ async function resolveViewStatePromises(viewState) {
2120
+ const entries = Object.entries(viewState);
2121
+ const hasPromises = entries.some(([, v]) => v instanceof Promise);
2122
+ if (!hasPromises) return { resolved: viewState, outcomes: [] };
2123
+ const result = { ...viewState };
2124
+ const outcomes = [];
2125
+ for (const [key, value] of entries) {
2126
+ if (value instanceof Promise) {
2127
+ try {
2128
+ const val = await value;
2129
+ result[key] = val;
2130
+ outcomes.push({ id: key, status: "resolved", value: val });
2131
+ } catch (err) {
2132
+ delete result[key];
2133
+ outcomes.push({
2134
+ id: key,
2135
+ status: "rejected",
2136
+ value: { message: err?.message ?? String(err) }
2137
+ });
2138
+ }
2139
+ }
2140
+ }
2141
+ return { resolved: result, outcomes };
2142
+ }
2143
+ async function generateClientScript(defaultViewState, fastCarryForward, parts, jayHtmlPath, trackByMap = {}, clientInitData = {}, projectInit, pluginInits = [], options = {}) {
2144
+ const { resolved, outcomes } = await resolveViewStatePromises(defaultViewState);
2145
+ defaultViewState = resolved;
2146
+ const {
2147
+ partImports,
2148
+ compositeParts,
2149
+ pluginClientInitImports,
2150
+ projectInitImport,
2151
+ clientInitExecution,
2152
+ automationImport,
2153
+ slowViewStateDecl
2154
+ } = buildScriptFragments(parts, clientInitData, projectInit, pluginInits, options);
2155
+ const automationWrap = buildAutomationWrap(options, "client");
2156
+ return `<!doctype html>
2157
+ <html lang="en">
2158
+ <head>
2159
+ <meta charset="UTF-8" />
2160
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
2161
+ <title>Vite + TS</title>
2162
+ </head>
2163
+ <body>
2164
+ <div id="target"></div>
2165
+ <script type="module">
2166
+ import {makeCompositeJayComponent} from "@jay-framework/stack-client-runtime";
2167
+ ${automationImport}
2168
+ ${pluginClientInitImports}
2169
+ ${projectInitImport}
2170
+ import { render } from '${jayHtmlPath}';
2171
+ ${partImports}${slowViewStateDecl}
2172
+ const viewState = ${JSON.stringify(defaultViewState)};
2173
+ ${generatePromiseReconstruction(outcomes)}
2174
+ const fastCarryForward = ${JSON.stringify(fastCarryForward)};
2175
+ const trackByMap = ${JSON.stringify(trackByMap)};
2176
+ ${clientInitExecution}
2177
+ const target = document.getElementById('target');
2178
+ const pageComp = makeCompositeJayComponent(render, viewState, fastCarryForward, ${compositeParts}, trackByMap)
2179
+
2180
+ const instance = pageComp({/* placeholder for page props */})
2181
+ ${automationWrap}
2182
+ <\/script>
2183
+ </body>
2184
+ </html>`;
2185
+ }
2186
+ const serverModuleCache = /* @__PURE__ */ new Map();
2187
+ function clearServerElementCache() {
2188
+ serverModuleCache.clear();
2189
+ }
2190
+ function getByPath(obj, dotPath) {
2191
+ const segments = dotPath.split(".");
2192
+ let current = obj;
2193
+ for (const seg of segments) {
2194
+ if (current == null || typeof current !== "object") return void 0;
2195
+ current = current[seg];
2196
+ }
2197
+ return current;
2198
+ }
2199
+ function resolveParts(parts, viewState) {
2200
+ return parts.map((p) => {
2201
+ if (p.kind === "static") return p.value;
2202
+ if (!viewState) return `{${p.value}}`;
2203
+ const resolved = getByPath(viewState, p.value);
2204
+ return resolved !== void 0 && resolved !== null ? String(resolved) : `{${p.value}}`;
2205
+ }).join("");
2206
+ }
2207
+ function headMetaToHeadTags(headMeta, viewState) {
2208
+ if (!headMeta) return [];
2209
+ const tags = [];
2210
+ if (headMeta.title) {
2211
+ tags.push({ tag: "title", children: resolveParts(headMeta.title, viewState) });
2212
+ }
2213
+ for (const m of headMeta.meta) {
2214
+ const attrs = {};
2215
+ if (m.name) attrs.name = m.name;
2216
+ if (m.property) attrs.property = m.property;
2217
+ attrs.content = resolveParts(m.content, viewState);
2218
+ tags.push({ tag: "meta", attrs });
2219
+ }
2220
+ for (const l of headMeta.links) {
2221
+ if (l.rel === "stylesheet" || l.rel === "import") continue;
2222
+ const attrs = { rel: l.rel };
2223
+ attrs.href = resolveParts(l.href, viewState);
2224
+ for (const [k, v] of Object.entries(l)) {
2225
+ if (k !== "rel" && k !== "href" && typeof v === "string") attrs[k] = v;
2226
+ }
2227
+ tags.push({ tag: "link", attrs });
2228
+ }
2229
+ return tags;
2230
+ }
2231
+ async function generateSSRPageHtml(vite, jayHtmlContent, jayHtmlFilename, jayHtmlDir, viewState, jayHtmlImportPath, parts, carryForward, trackByMap = {}, clientInitData = {}, buildFolder, projectRoot, routeDir, tsConfigFilePath, projectInit, pluginInits = [], options = {}, sourceDir, headTags, scripts) {
2232
+ const jayHtmlPath = path__default.join(jayHtmlDir, jayHtmlFilename);
2233
+ let cached = serverModuleCache.get(jayHtmlPath);
2234
+ if (!cached) {
2235
+ cached = await compileAndLoadServerElement(
2236
+ vite,
2237
+ jayHtmlContent,
2238
+ jayHtmlFilename,
2239
+ jayHtmlDir,
2240
+ buildFolder,
2241
+ projectRoot,
2242
+ routeDir,
2243
+ tsConfigFilePath,
2244
+ sourceDir
2245
+ );
2246
+ serverModuleCache.set(jayHtmlPath, cached);
2247
+ }
2248
+ const htmlChunks = [];
2249
+ const asyncPromises = [];
2250
+ const asyncOutcomes = [];
2251
+ const ctx = {
2252
+ write: (chunk) => {
2253
+ htmlChunks.push(chunk);
2254
+ },
2255
+ onAsync: (promise, id, templates) => {
2256
+ const asyncPromise = promise.then(
2257
+ (val) => {
2258
+ asyncOutcomes.push({ id, status: "resolved", value: val });
2259
+ if (templates.resolved) {
2260
+ return asyncSwapScript(id, templates.resolved(val));
2261
+ }
2262
+ return "";
2263
+ },
2264
+ (err) => {
2265
+ asyncOutcomes.push({
2266
+ id,
2267
+ status: "rejected",
2268
+ value: { message: err?.message ?? String(err) }
2269
+ });
2270
+ if (templates.rejected) {
2271
+ return asyncSwapScript(id, templates.rejected(err));
2272
+ }
2273
+ return "";
2274
+ }
2275
+ );
2276
+ asyncPromises.push(asyncPromise);
2277
+ }
2278
+ };
2279
+ cached.renderToStream(viewState, ctx);
2280
+ const asyncResults = await Promise.all(asyncPromises);
2281
+ const ssrHtml = htmlChunks.join("");
2282
+ const asyncScripts = asyncResults.filter((r) => r !== "").join("");
2283
+ const hydrationScript = generateHydrationScript(
2284
+ viewState,
2285
+ carryForward,
2286
+ parts,
2287
+ jayHtmlImportPath,
2288
+ trackByMap,
2289
+ clientInitData,
2290
+ projectInit,
2291
+ pluginInits,
2292
+ options,
2293
+ asyncOutcomes
2294
+ );
2295
+ const cssLink = cached.cssHref ? ` <link rel="stylesheet" href="${cached.cssHref}" />` : "";
2296
+ const tagSources = [];
2297
+ if (headTags && headTags.length > 0) tagSources.push(headTags);
2298
+ const headMetaTags = headMetaToHeadTags(cached.headMeta, viewState);
2299
+ if (headMetaTags.length > 0) tagSources.push(headMetaTags);
2300
+ const mergedTags = mergeHeadTags(tagSources);
2301
+ const hasCustomTitle = mergedTags.some((t) => t.tag.toLowerCase() === "title");
2302
+ const titleTag = hasCustomTitle ? "" : " <title>Vite + TS</title>\n";
2303
+ const headTagsHtml = mergedTags.length > 0 ? serializeHeadTags(mergedTags) : "";
2304
+ const headScriptsHtml = serializeScripts(scripts, "head");
2305
+ const bodyScriptsHtml = serializeScripts(scripts, "body");
2306
+ const headExtras = [cssLink, headTagsHtml, headScriptsHtml].filter((_) => _).join("\n");
2307
+ return `<!doctype html>
2308
+ <html lang="en">
2309
+ <head>
2310
+ <meta charset="UTF-8" />
2311
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
2312
+ ${titleTag}${headExtras ? headExtras + "\n" : ""} </head>
2313
+ <body>
2314
+ <div id="target">${ssrHtml}</div>${asyncScripts}
2315
+ ${hydrationScript}${bodyScriptsHtml ? "\n " + bodyScriptsHtml : ""}
2316
+ </body>
2317
+ </html>`;
2318
+ }
2319
+ async function generateFrozenPageHtml(vite, jayHtmlContent, jayHtmlFilename, jayHtmlDir, viewState, buildFolder, projectRoot, routeDir, tsConfigFilePath, sourceDir, format = "page", freezeName, options) {
2320
+ const jayHtmlPath = path__default.join(jayHtmlDir, jayHtmlFilename);
2321
+ let cached = serverModuleCache.get(jayHtmlPath);
2322
+ if (!cached) {
2323
+ cached = await compileAndLoadServerElement(
2324
+ vite,
2325
+ jayHtmlContent,
2326
+ jayHtmlFilename,
2327
+ jayHtmlDir,
2328
+ buildFolder,
2329
+ projectRoot,
2330
+ routeDir,
2331
+ tsConfigFilePath,
2332
+ sourceDir
2333
+ );
2334
+ serverModuleCache.set(jayHtmlPath, cached);
2335
+ }
2336
+ const htmlChunks = [];
2337
+ const ctx = {
2338
+ write: (chunk) => {
2339
+ htmlChunks.push(chunk);
2340
+ },
2341
+ onAsync: () => {
2342
+ }
2343
+ };
2344
+ cached.renderToStream(viewState, ctx);
2345
+ const ssrHtml = htmlChunks.join("");
2346
+ if (format === "fragment") {
2347
+ let inlineCss = "";
2348
+ if (cached.cssHref) {
2349
+ try {
2350
+ const cssPath = cached.cssHref.replace(/^\/@fs/, "").replace(/\?.*$/, "");
2351
+ const cssContent = await fs__default.readFile(cssPath, "utf-8");
2352
+ inlineCss = `<style>${cssContent}</style>`;
2353
+ } catch {
2354
+ inlineCss = `<link rel="stylesheet" href="${cached.cssHref}" />`;
2355
+ }
2356
+ }
2357
+ return `${inlineCss}
2358
+ ${ssrHtml}`;
2359
+ }
2360
+ const frozenHeadTags = headMetaToHeadTags(cached.headMeta, viewState);
2361
+ const frozenHeadTagsHtml = frozenHeadTags.length > 0 ? serializeHeadTags(frozenHeadTags) : "";
2362
+ const cssLink = cached.cssHref ? ` <link rel="stylesheet" href="${cached.cssHref}" />` : "";
2363
+ const headExtras = [cssLink, frozenHeadTagsHtml].filter((_) => _).join("\n");
2364
+ const label = freezeName ? ` — ${freezeName}` : "";
2365
+ const devHmrScript = options?.injectDevHmr === true ? `
2366
+ <script type="module">${buildPageReloadHmrScript()}
2367
+ <\/script>` : "";
2368
+ return `<!doctype html>
2369
+ <html lang="en">
2370
+ <head>
2371
+ <meta charset="UTF-8" />
2372
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
2373
+ <title>Frozen${label}</title>
2374
+ ${headExtras ? headExtras + "\n" : ""} <style>
2375
+ body::before {
2376
+ content: 'FROZEN${label ? `: ${freezeName}` : ""}';
2377
+ position: fixed;
2378
+ top: 0;
2379
+ right: 0;
2380
+ background: #1a1a2e;
2381
+ color: #e0e0ff;
2382
+ padding: 2px 10px;
2383
+ font: 11px/1.6 system-ui;
2384
+ z-index: 99999;
2385
+ border-bottom-left-radius: 4px;
2386
+ opacity: 0.8;
2387
+ }
2388
+ </style>
2389
+ </head>
2390
+ <body>
2391
+ <div id="target">${ssrHtml}</div>${devHmrScript}
2392
+ </body>
2393
+ </html>`;
2394
+ }
2395
+ function serializeScripts(scripts, position) {
2396
+ if (!scripts) return "";
2397
+ const filtered = scripts.filter((s2) => s2.position === position);
2398
+ if (filtered.length === 0) return "";
2399
+ return filtered.map((s2) => {
2400
+ const attrs = Object.entries(s2.attributes).map(([k, v]) => v === "" ? k : `${k}="${v}"`).join(" ");
2401
+ const attrStr = attrs ? " " + attrs : "";
2402
+ if (s2.src) {
2403
+ return ` <script src="${s2.src}"${attrStr}><\/script>`;
2404
+ }
2405
+ return ` <script${attrStr}>${s2.inline}<\/script>`;
2406
+ }).join("\n");
2407
+ }
2408
+ function rebaseRelativeImports(code, fromDir, toDir) {
2409
+ return code.replace(/from "(\.\.\/[^"]+)"/g, (_match, relPath) => {
2410
+ const absolutePath = path__default.resolve(fromDir, relPath);
2411
+ let newRelPath = path__default.relative(toDir, absolutePath);
2412
+ if (!newRelPath.startsWith(".")) {
2413
+ newRelPath = "./" + newRelPath;
2414
+ }
2415
+ return `from "${newRelPath}"`;
2416
+ });
2417
+ }
2418
+ async function compileAndLoadServerElement(vite, jayHtmlContent, jayHtmlFilename, jayHtmlDir, buildFolder, projectRoot, routeDir, tsConfigFilePath, sourceDir) {
2419
+ const jayFile = await parseJayFile(
2420
+ jayHtmlContent,
2421
+ jayHtmlFilename,
2422
+ jayHtmlDir,
2423
+ { relativePath: tsConfigFilePath },
2424
+ JAY_IMPORT_RESOLVER,
2425
+ projectRoot,
2426
+ sourceDir
2427
+ );
2428
+ const parsedJayFile = checkValidationErrors(jayFile);
2429
+ const pageName = jayHtmlFilename.replace(".jay-html", "");
2430
+ const debugCoordinatePreprocessPath = path__default.join(
2431
+ buildFolder,
2432
+ "debug",
2433
+ routeDir,
2434
+ `${pageName}.coordinate-preprocess.jay-html`
2435
+ );
2436
+ const serverElementOptions = {
2437
+ debugCoordinatePreprocessPath
2438
+ };
2439
+ const serverElementCode = checkValidationErrors(
2440
+ generateServerElementFile(parsedJayFile, serverElementOptions)
2441
+ );
2442
+ const serverElementDir = path__default.join(buildFolder, "pre-rendered", routeDir);
2443
+ await fs__default.mkdir(serverElementDir, { recursive: true });
2444
+ const adjustedCode = rebaseRelativeImports(serverElementCode, jayHtmlDir, serverElementDir);
2445
+ const serverElementFilename = jayHtmlFilename.replace(".jay-html", ".server-element.ts");
2446
+ const serverElementPath = path__default.join(serverElementDir, serverElementFilename);
2447
+ await fs__default.writeFile(serverElementPath, adjustedCode, "utf-8");
2448
+ const existingModule = vite.moduleGraph.getModuleById(serverElementPath);
2449
+ if (existingModule) {
2450
+ vite.moduleGraph.invalidateModule(existingModule);
2451
+ }
2452
+ const jayHtmlPath = path__default.join(serverElementDir, jayHtmlFilename);
2453
+ invalidateJayHtmlModules(vite, jayHtmlPath);
2454
+ const serverModule = await vite.ssrLoadModule(serverElementPath);
2455
+ let cssHref;
2456
+ if (parsedJayFile.css) {
2457
+ const cssFilename = jayHtmlFilename.replace(".jay-html", ".css");
2458
+ const cssPath = path__default.join(serverElementDir, cssFilename);
2459
+ await fs__default.writeFile(cssPath, parsedJayFile.css, "utf-8");
2460
+ const cssModules = vite.moduleGraph.getModulesByFile(cssPath);
2461
+ if (cssModules) {
2462
+ for (const mod of cssModules) {
2463
+ vite.moduleGraph.invalidateModule(mod);
2464
+ }
2465
+ }
2466
+ const hash = crypto.createHash("md5").update(parsedJayFile.css).digest("hex").slice(0, 8);
2467
+ cssHref = "/@fs" + cssPath + "?v=" + hash + "&direct";
2468
+ }
2469
+ return {
2470
+ renderToStream: serverModule.renderToStream,
2471
+ headLinks: parsedJayFile.headLinks,
2472
+ headMeta: parsedJayFile.headMeta,
2473
+ cssHref
2474
+ };
2475
+ }
2476
+ function invalidateJayHtmlModules(vite, jayHtmlPath) {
2477
+ let count = 0;
2478
+ const byFile = vite.moduleGraph.getModulesByFile(jayHtmlPath);
2479
+ if (byFile) {
2480
+ for (const mod of byFile) {
2481
+ vite.moduleGraph.invalidateModule(mod);
2482
+ count++;
2483
+ }
2484
+ }
2485
+ const knownIds = [jayHtmlPath + ".ts", jayHtmlPath + JAY_QUERY_HYDRATE + ".ts"];
2486
+ for (const id of knownIds) {
2487
+ const mod = vite.moduleGraph.getModuleById(id);
2488
+ if (mod) {
2489
+ vite.moduleGraph.invalidateModule(mod);
2490
+ count++;
2491
+ }
2492
+ }
2493
+ const idMap = vite.moduleGraph.idToModuleMap;
2494
+ for (const [id, mod] of idMap) {
2495
+ if (id.includes(jayHtmlPath)) {
2496
+ vite.moduleGraph.invalidateModule(mod);
2497
+ count++;
2498
+ }
2499
+ }
2500
+ if (count > 0) {
2501
+ getLogger().info(`[SSR] Invalidated ${count} Vite module(s) for ${jayHtmlPath}`);
2502
+ }
2503
+ }
2504
+ function generateHydrationScript(defaultViewState, fastCarryForward, parts, jayHtmlPath, trackByMap = {}, clientInitData = {}, projectInit, pluginInits = [], options = {}, asyncOutcomes = []) {
2505
+ const {
2506
+ partImports,
2507
+ compositeParts,
2508
+ pluginClientInitImports,
2509
+ projectInitImport,
2510
+ clientInitExecution,
2511
+ automationImport,
2512
+ slowViewStateDecl
2513
+ } = buildScriptFragments(parts, clientInitData, projectInit, pluginInits, options);
2514
+ const automationWrap = buildAutomationWrap(options, "hydrate");
2515
+ const hydrateImportPath = `${jayHtmlPath}${JAY_QUERY_HYDRATE}`;
2516
+ return `<script type="module">
2517
+ import {hydrateCompositeJayComponent} from "@jay-framework/stack-client-runtime";
2518
+ ${automationImport}
2519
+ ${pluginClientInitImports}
2520
+ ${projectInitImport}
2521
+ import { hydrate } from '${hydrateImportPath}';
2522
+ ${partImports}${slowViewStateDecl}
2523
+ const viewState = ${JSON.stringify(defaultViewState)};
2524
+ ${generatePromiseReconstruction(asyncOutcomes)} const fastCarryForward = ${JSON.stringify(fastCarryForward)};
2525
+ const trackByMap = ${JSON.stringify(trackByMap)};
2526
+
2527
+ const target = document.getElementById('target');
2528
+ const rootElement = target.firstElementChild;
2529
+ const pageComp = hydrateCompositeJayComponent(hydrate, viewState, fastCarryForward, ${compositeParts}, trackByMap, rootElement);
2530
+ ${clientInitExecution}
2531
+ const instance = pageComp({/* placeholder for page props */});
2532
+ ${automationWrap}
2533
+ <\/script>`;
2534
+ }
2535
+ function parseActionMetadata(yamlContent, fileName) {
2536
+ try {
2537
+ const parsed = parseAction(yamlContent, fileName);
2538
+ if (parsed.validations.length > 0) {
2539
+ getLogger().warn(
2540
+ `[ActionMetadata] ${fileName}: validation errors: ${parsed.validations.join(", ")}`
2541
+ );
2542
+ return null;
2543
+ }
2544
+ if (!parsed.val) {
2545
+ getLogger().warn(`[ActionMetadata] ${fileName}: parsing returned no result`);
2546
+ return null;
2547
+ }
2548
+ const action = parsed.val;
2549
+ const inputJsonSchema = jayTypeToJsonSchema(action.inputType);
2550
+ let inputSchema;
2551
+ if (inputJsonSchema && inputJsonSchema.type === "object") {
2552
+ inputSchema = {
2553
+ type: "object",
2554
+ properties: inputJsonSchema.properties || {},
2555
+ ...inputJsonSchema.required && inputJsonSchema.required.length > 0 && { required: inputJsonSchema.required }
2556
+ };
2557
+ } else {
2558
+ inputSchema = { type: "object", properties: {} };
2559
+ }
2560
+ const metadata = {
2561
+ name: action.name,
2562
+ description: action.description,
2563
+ inputSchema
2564
+ };
2565
+ if (action.outputType) {
2566
+ const outputJsonSchema = jayTypeToJsonSchema(action.outputType);
2567
+ if (outputJsonSchema) {
2568
+ metadata.outputSchema = outputJsonSchema;
2569
+ }
2570
+ }
2571
+ return metadata;
2572
+ } catch (error) {
2573
+ getLogger().error(
2574
+ `[ActionMetadata] Failed to parse ${fileName}: ${error instanceof Error ? error.message : error}`
2575
+ );
2576
+ return null;
2577
+ }
2578
+ }
2579
+ function loadActionMetadata(filePath) {
2580
+ if (!fs.existsSync(filePath)) {
2581
+ getLogger().warn(`[ActionMetadata] File not found: ${filePath}`);
2582
+ return null;
2583
+ }
2584
+ const yamlContent = fs.readFileSync(filePath, "utf-8");
2585
+ const fileName = path.basename(filePath);
2586
+ return parseActionMetadata(yamlContent, fileName);
2587
+ }
2588
+ function resolveActionMetadataPath(actionPath, pluginDir) {
2589
+ return path.resolve(pluginDir, actionPath);
2590
+ }
2591
+ const require$2 = createRequire$1(import.meta.url);
2592
+ async function discoverAndRegisterActions(options) {
2593
+ const {
2594
+ projectRoot,
2595
+ actionsDir = "src/actions",
2596
+ registry = actionRegistry,
2597
+ verbose = false,
2598
+ viteServer
2599
+ } = options;
2600
+ const result = {
2601
+ actionCount: 0,
2602
+ actionNames: [],
2603
+ scannedFiles: []
2604
+ };
2605
+ const actionsPath = path.resolve(projectRoot, actionsDir);
2606
+ if (!fs.existsSync(actionsPath)) {
2607
+ if (verbose) {
2608
+ getLogger().info(`[Actions] No actions directory found at ${actionsPath}`);
2609
+ }
2610
+ return result;
2611
+ }
2612
+ const actionFiles = await findActionFiles(actionsPath);
2613
+ if (verbose) {
2614
+ getLogger().info(`[Actions] Found ${actionFiles.length} action file(s)`);
2615
+ }
2616
+ for (const filePath of actionFiles) {
2617
+ result.scannedFiles.push(filePath);
2618
+ try {
2619
+ let module;
2620
+ if (viteServer) {
2621
+ module = await viteServer.ssrLoadModule(filePath);
2622
+ } else {
2623
+ module = await import(filePath);
2624
+ }
2625
+ for (const [exportName, exportValue] of Object.entries(module)) {
2626
+ if (isJayAction(exportValue)) {
2627
+ registry.register(exportValue);
2628
+ result.actionNames.push(exportValue.actionName);
2629
+ result.actionCount++;
2630
+ if (verbose) {
2631
+ getLogger().info(
2632
+ `[Actions] Registered: ${exportValue.actionName}`
2633
+ );
2634
+ }
2635
+ } else if (isJayStreamAction(exportValue)) {
2636
+ registry.registerStream(exportValue);
2637
+ result.actionNames.push(exportValue.actionName);
2638
+ result.actionCount++;
2639
+ if (verbose) {
2640
+ getLogger().info(
2641
+ `[Actions] Registered stream: ${exportValue.actionName}`
2642
+ );
2643
+ }
2644
+ }
2645
+ }
2646
+ } catch (error) {
2647
+ getLogger().error(`[Actions] Failed to import ${filePath}: ${error}`);
2648
+ }
2649
+ }
2650
+ return result;
2651
+ }
2652
+ async function findActionFiles(dir) {
2653
+ const files = [];
2654
+ const entries = await fs.promises.readdir(dir, { withFileTypes: true });
2655
+ for (const entry of entries) {
2656
+ const fullPath = path.join(dir, entry.name);
2657
+ if (entry.isDirectory()) {
2658
+ const subFiles = await findActionFiles(fullPath);
2659
+ files.push(...subFiles);
2660
+ } else if (entry.isFile() && entry.name.endsWith(".actions.ts")) {
2661
+ files.push(fullPath);
2662
+ }
2663
+ }
2664
+ return files;
2665
+ }
2666
+ async function discoverAllPluginActions(options) {
2667
+ const { projectRoot, registry = actionRegistry, verbose = false, viteServer } = options;
2668
+ const allActions = [];
2669
+ const localPluginsPath = path.join(projectRoot, "src/plugins");
2670
+ if (fs.existsSync(localPluginsPath)) {
2671
+ const pluginDirs = await fs.promises.readdir(localPluginsPath, { withFileTypes: true });
2672
+ for (const entry of pluginDirs) {
2673
+ if (entry.isDirectory()) {
2674
+ const pluginPath = path.join(localPluginsPath, entry.name);
2675
+ const actions = await discoverPluginActions(
2676
+ pluginPath,
2677
+ projectRoot,
2678
+ registry,
2679
+ verbose,
2680
+ viteServer
2681
+ );
2682
+ allActions.push(...actions);
2683
+ }
2684
+ }
2685
+ }
2686
+ const npmActions = await discoverNpmPluginActions(projectRoot, registry, verbose, viteServer);
2687
+ allActions.push(...npmActions);
2688
+ return allActions;
2689
+ }
2690
+ async function discoverNpmPluginActions(projectRoot, registry, verbose, viteServer) {
2691
+ const allActions = [];
2692
+ const packageJsonPath = path.join(projectRoot, "package.json");
2693
+ if (!fs.existsSync(packageJsonPath)) {
2694
+ return allActions;
2695
+ }
2696
+ try {
2697
+ const packageJson = JSON.parse(await fs.promises.readFile(packageJsonPath, "utf-8"));
2698
+ const dependencies = {
2699
+ ...packageJson.dependencies,
2700
+ ...packageJson.devDependencies
2701
+ };
2702
+ for (const packageName of Object.keys(dependencies)) {
2703
+ try {
2704
+ const pluginYamlPath = tryResolvePluginYaml(packageName, projectRoot);
2705
+ if (!pluginYamlPath) {
2706
+ continue;
2707
+ }
2708
+ const pluginDir = path.dirname(pluginYamlPath);
2709
+ const pluginConfig = loadPluginManifest(pluginDir);
2710
+ if (!pluginConfig || !pluginConfig.actions || !Array.isArray(pluginConfig.actions)) {
2711
+ continue;
2712
+ }
2713
+ if (verbose) {
2714
+ getLogger().info(
2715
+ `[Actions] NPM plugin "${packageName}" declares actions: ${JSON.stringify(pluginConfig.actions)}`
2716
+ );
2717
+ }
2718
+ const actions = await registerNpmPluginActions(
2719
+ packageName,
2720
+ pluginConfig,
2721
+ pluginDir,
2722
+ registry,
2723
+ verbose,
2724
+ viteServer
2725
+ );
2726
+ allActions.push(...actions);
2727
+ } catch {
2728
+ continue;
2729
+ }
2730
+ }
2731
+ } catch (error) {
2732
+ getLogger().error(`[Actions] Failed to read project package.json: ${error}`);
2733
+ }
2734
+ return allActions;
2735
+ }
2736
+ function tryResolvePluginYaml(packageName, projectRoot) {
2737
+ try {
2738
+ return require$2.resolve(`${packageName}/plugin.yaml`, {
2739
+ paths: [projectRoot]
2740
+ });
2741
+ } catch {
2742
+ return null;
2743
+ }
2744
+ }
2745
+ function resolveNpmActionMetadataPath(actionPath, packageName, pluginDir) {
2746
+ if (!actionPath.startsWith(".")) {
2747
+ try {
2748
+ return require$2.resolve(`${packageName}/${actionPath}`, {
2749
+ paths: [pluginDir]
2750
+ });
2751
+ } catch {
2752
+ }
2753
+ }
2754
+ const resolved = resolveActionMetadataPath(actionPath, pluginDir);
2755
+ if (fs.existsSync(resolved)) {
2756
+ return resolved;
2757
+ }
2758
+ getLogger().warn(
2759
+ `[Actions] Could not resolve .jay-action file "${actionPath}" for package "${packageName}"`
2760
+ );
2761
+ return null;
2762
+ }
2763
+ async function registerNpmPluginActions(packageName, pluginConfig, pluginDir, registry, verbose, viteServer) {
2764
+ const registeredActions = [];
2765
+ try {
2766
+ let pluginModule;
2767
+ if (viteServer) {
2768
+ pluginModule = await viteServer.ssrLoadModule(packageName);
2769
+ } else {
2770
+ pluginModule = await import(packageName);
2771
+ }
2772
+ let toolsModule;
2773
+ const loadToolsModule = async () => {
2774
+ if (!toolsModule) {
2775
+ const toolsEntry = `${packageName}/tools`;
2776
+ toolsModule = viteServer ? await viteServer.ssrLoadModule(toolsEntry) : await import(toolsEntry);
2777
+ }
2778
+ return toolsModule;
2779
+ };
2780
+ for (const entry of pluginConfig.actions) {
2781
+ const { name: actionName, action: actionPath, devOnly } = normalizeActionEntry(entry);
2782
+ const sourceModule = devOnly ? await loadToolsModule() : pluginModule;
2783
+ const actionExport = sourceModule[actionName];
2784
+ if (actionExport && isJayAction(actionExport)) {
2785
+ registry.register(actionExport);
2786
+ const registeredName = actionExport.actionName;
2787
+ registeredActions.push(registeredName);
2788
+ if (actionPath) {
2789
+ const metadataFilePath = resolveNpmActionMetadataPath(
2790
+ actionPath,
2791
+ packageName,
2792
+ pluginDir
2793
+ );
2794
+ if (metadataFilePath) {
2795
+ const metadata = loadActionMetadata(metadataFilePath);
2796
+ if (metadata) {
2797
+ registry.setMetadata(registeredName, metadata);
2798
+ if (verbose) {
2799
+ getLogger().info(
2800
+ `[Actions] Loaded metadata for "${registeredName}" from ${actionPath}`
2801
+ );
2802
+ }
2803
+ }
2804
+ }
2805
+ }
2806
+ if (verbose) {
2807
+ getLogger().info(`[Actions] Registered NPM plugin action: ${registeredName}`);
2808
+ }
2809
+ } else if (actionExport && isJayStreamAction(actionExport)) {
2810
+ registry.registerStream(actionExport);
2811
+ const registeredName = actionExport.actionName;
2812
+ registeredActions.push(registeredName);
2813
+ if (verbose) {
2814
+ getLogger().info(`[Actions] Registered NPM plugin stream: ${registeredName}`);
2815
+ }
2816
+ } else {
2817
+ getLogger().warn(
2818
+ `[Actions] NPM plugin "${packageName}" declares action "${actionName}" but it's not exported or not a JayAction`
2819
+ );
2820
+ }
2821
+ }
2822
+ } catch (importError) {
2823
+ getLogger().error(`[Actions] Failed to import NPM plugin "${packageName}": ${importError}`);
2824
+ }
2825
+ return registeredActions;
2826
+ }
2827
+ async function discoverPluginActions(pluginPath, projectRoot, registry = actionRegistry, verbose = false, viteServer) {
2828
+ const pluginConfig = loadPluginManifest(pluginPath);
2829
+ if (!pluginConfig) {
2830
+ return [];
2831
+ }
2832
+ if (!pluginConfig.actions || !Array.isArray(pluginConfig.actions)) {
2833
+ return [];
2834
+ }
2835
+ const registeredActions = [];
2836
+ const pluginName = pluginConfig.name || path.basename(pluginPath);
2837
+ if (verbose) {
2838
+ getLogger().info(
2839
+ `[Actions] Plugin "${pluginName}" declares actions: ${JSON.stringify(pluginConfig.actions)}`
2840
+ );
2841
+ }
2842
+ let modulePath = pluginConfig.module ? path.join(pluginPath, pluginConfig.module) : path.join(pluginPath, "index.ts");
2843
+ if (!fs.existsSync(modulePath)) {
2844
+ const tsPath = modulePath + ".ts";
2845
+ const jsPath = modulePath + ".js";
2846
+ if (fs.existsSync(tsPath)) {
2847
+ modulePath = tsPath;
2848
+ } else if (fs.existsSync(jsPath)) {
2849
+ modulePath = jsPath;
2850
+ } else {
2851
+ getLogger().warn(`[Actions] Plugin "${pluginName}" module not found at ${modulePath}`);
2852
+ return [];
2853
+ }
2854
+ }
2855
+ const resolveLocalToolsModulePath = () => {
2856
+ for (const candidate of ["tools.ts", "tools.js"]) {
2857
+ const candidatePath = path.join(pluginPath, candidate);
2858
+ if (fs.existsSync(candidatePath)) return candidatePath;
2859
+ }
2860
+ return null;
2861
+ };
2862
+ try {
2863
+ let pluginModule;
2864
+ if (viteServer) {
2865
+ pluginModule = await viteServer.ssrLoadModule(modulePath);
2866
+ } else {
2867
+ pluginModule = await import(modulePath);
2868
+ }
2869
+ let toolsModule;
2870
+ const loadToolsModule = async () => {
2871
+ if (toolsModule) return toolsModule;
2872
+ const toolsPath = resolveLocalToolsModulePath();
2873
+ if (!toolsPath) {
2874
+ getLogger().warn(
2875
+ `[Actions] Plugin "${pluginName}" declares a devOnly action but has no tools.ts/tools.js module`
2876
+ );
2877
+ return void 0;
2878
+ }
2879
+ toolsModule = viteServer ? await viteServer.ssrLoadModule(toolsPath) : await import(toolsPath);
2880
+ return toolsModule;
2881
+ };
2882
+ for (const entry of pluginConfig.actions) {
2883
+ const { name: actionName, action: actionPath, devOnly } = normalizeActionEntry(entry);
2884
+ const sourceModule = devOnly ? await loadToolsModule() : pluginModule;
2885
+ const actionExport = sourceModule?.[actionName];
2886
+ if (actionExport && isJayAction(actionExport)) {
2887
+ registry.register(actionExport);
2888
+ const registeredName = actionExport.actionName;
2889
+ registeredActions.push(registeredName);
2890
+ if (actionPath) {
2891
+ const metadataFilePath = resolveActionMetadataPath(actionPath, pluginPath);
2892
+ const metadata = loadActionMetadata(metadataFilePath);
2893
+ if (metadata) {
2894
+ registry.setMetadata(registeredName, metadata);
2895
+ if (verbose) {
2896
+ getLogger().info(
2897
+ `[Actions] Loaded metadata for "${registeredName}" from ${actionPath}`
2898
+ );
2899
+ }
2900
+ }
2901
+ }
2902
+ if (verbose) {
2903
+ getLogger().info(`[Actions] Registered plugin action: ${registeredName}`);
2904
+ }
2905
+ } else if (actionExport && isJayStreamAction(actionExport)) {
2906
+ registry.registerStream(actionExport);
2907
+ const registeredName = actionExport.actionName;
2908
+ registeredActions.push(registeredName);
2909
+ if (verbose) {
2910
+ getLogger().info(`[Actions] Registered plugin stream: ${registeredName}`);
2911
+ }
2912
+ } else {
2913
+ getLogger().warn(
2914
+ `[Actions] Plugin "${pluginName}" declares action "${actionName}" but it's not exported or not a JayAction`
2915
+ );
2916
+ }
2917
+ }
2918
+ } catch (importError) {
2919
+ getLogger().error(
2920
+ `[Actions] Failed to import plugin module at ${modulePath}: ${importError}`
2921
+ );
2922
+ }
2923
+ return registeredActions;
2924
+ }
2925
+ const require$1 = createRequire(import.meta.url);
2926
+ async function executeDynamicGenerator(plugin, config, projectRoot, services, verbose, viteServer) {
2927
+ const { pluginPath, name: pluginName, isLocal, packageName } = plugin;
2928
+ if (!config.generator) {
2929
+ throw new Error(
2930
+ `Plugin "${pluginName}" has dynamic_contracts entry but no generator specified`
2931
+ );
2932
+ }
2933
+ const isFilePath = config.generator.startsWith("./") || config.generator.startsWith("/") || config.generator.includes(".ts") || config.generator.includes(".js");
2934
+ let generator;
2935
+ if (isFilePath) {
2936
+ let generatorPath;
2937
+ if (!isLocal) {
2938
+ try {
2939
+ generatorPath = require$1.resolve(`${packageName}/${config.generator}`, {
2940
+ paths: [projectRoot]
2941
+ });
2942
+ } catch {
2943
+ generatorPath = path.join(pluginPath, config.generator);
2944
+ }
2945
+ } else {
2946
+ generatorPath = path.join(pluginPath, config.generator);
2947
+ }
2948
+ if (!fs.existsSync(generatorPath)) {
2949
+ const withTs = generatorPath + ".ts";
2950
+ const withJs = generatorPath + ".js";
2951
+ if (fs.existsSync(withTs)) {
2952
+ generatorPath = withTs;
2953
+ } else if (fs.existsSync(withJs)) {
2954
+ generatorPath = withJs;
2955
+ }
2956
+ }
2957
+ if (!fs.existsSync(generatorPath)) {
2958
+ throw new Error(
2959
+ `Generator file not found for plugin "${pluginName}": ${config.generator}`
2960
+ );
2961
+ }
2962
+ if (verbose) {
2963
+ getLogger().info(` Loading generator from file: ${generatorPath}`);
2964
+ }
2965
+ let generatorModule;
2966
+ if (viteServer) {
2967
+ generatorModule = await viteServer.ssrLoadModule(generatorPath);
2968
+ } else {
2969
+ generatorModule = await import(generatorPath);
2970
+ }
2971
+ generator = generatorModule.generator || generatorModule.default;
2972
+ } else {
2973
+ if (verbose) {
2974
+ getLogger().info(
2975
+ ` Loading generator export: ${config.generator} from ${packageName}`
2976
+ );
2977
+ }
2978
+ let pluginModule;
2979
+ if (viteServer) {
2980
+ pluginModule = await viteServer.ssrLoadModule(packageName);
2981
+ } else {
2982
+ pluginModule = await import(packageName);
2983
+ }
2984
+ generator = pluginModule[config.generator];
2985
+ if (!generator) {
2986
+ throw new Error(
2987
+ `Generator "${config.generator}" not exported from plugin "${pluginName}". Ensure it's exported from the package's index.ts`
2988
+ );
2989
+ }
2990
+ }
2991
+ if (!generator || typeof generator.generate !== "function") {
2992
+ throw new Error(
2993
+ `Generator "${config.generator}" for plugin "${pluginName}" must have a 'generate' function. Use makeContractGenerator() to create valid generators.`
2994
+ );
2995
+ }
2996
+ const resolvedServices = [];
2997
+ for (const marker of generator.services) {
2998
+ const service = services.get(marker);
2999
+ if (!service) {
3000
+ const markerName = marker.description ?? "unknown";
3001
+ throw new Error(
3002
+ `Service "${markerName}" required by ${pluginName} generator not found. Ensure it's registered in init.ts`
3003
+ );
3004
+ }
3005
+ resolvedServices.push(service);
3006
+ }
3007
+ if (verbose) {
3008
+ getLogger().info(` Executing generator...`);
3009
+ }
3010
+ return await generator.generate(...resolvedServices);
3011
+ }
3012
+ function resolveStaticContractPath(plugin, contractSpec, projectRoot) {
3013
+ const { pluginPath, isLocal, packageName } = plugin;
3014
+ if (!isLocal) {
3015
+ try {
3016
+ return require$1.resolve(`${packageName}/${contractSpec}`, {
3017
+ paths: [projectRoot]
3018
+ });
3019
+ } catch {
3020
+ const possiblePaths = [
3021
+ path.join(pluginPath, "dist", contractSpec),
3022
+ path.join(pluginPath, "lib", contractSpec),
3023
+ path.join(pluginPath, contractSpec)
3024
+ ];
3025
+ const found = possiblePaths.find((p) => fs.existsSync(p));
3026
+ return found || possiblePaths[0];
3027
+ }
3028
+ } else {
3029
+ return path.join(pluginPath, contractSpec);
3030
+ }
3031
+ }
3032
+ function resolveActionFilePath(actionPath, packageName, pluginPath, isLocal, projectRoot) {
3033
+ if (!isLocal && !actionPath.startsWith(".")) {
3034
+ try {
3035
+ return require$1.resolve(`${packageName}/${actionPath}`, {
3036
+ paths: [projectRoot]
3037
+ });
3038
+ } catch {
3039
+ const possiblePaths = [
3040
+ path.join(pluginPath, "dist", actionPath),
3041
+ path.join(pluginPath, "lib", actionPath),
3042
+ path.join(pluginPath, actionPath)
3043
+ ];
3044
+ const found = possiblePaths.find((p) => fs.existsSync(p));
3045
+ return found || null;
3046
+ }
3047
+ }
3048
+ const resolved = resolveActionMetadataPath(actionPath, pluginPath);
3049
+ return fs.existsSync(resolved) ? resolved : null;
3050
+ }
3051
+ function toKebabCase(str) {
3052
+ return str.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "");
3053
+ }
3054
+ async function materializeContracts(options, services = /* @__PURE__ */ new Map()) {
3055
+ const {
3056
+ projectRoot,
3057
+ outputDir = path.join(projectRoot, "agent-kit", "materialized-contracts"),
3058
+ dynamicOnly = false,
3059
+ pluginFilter,
3060
+ verbose = false,
3061
+ viteServer
3062
+ } = options;
3063
+ const pluginsIndexMap = /* @__PURE__ */ new Map();
3064
+ let staticCount = 0;
3065
+ let dynamicCount = 0;
3066
+ if (verbose) {
3067
+ getLogger().info("Scanning for plugins...");
3068
+ }
3069
+ const plugins = await scanPlugins({
3070
+ projectRoot,
3071
+ verbose,
3072
+ includeDevDeps: true
3073
+ // Include dev deps for contract discovery
3074
+ });
3075
+ if (verbose) {
3076
+ getLogger().info(`Found ${plugins.size} plugin(s)`);
3077
+ }
3078
+ for (const [pluginKey, plugin] of plugins) {
3079
+ if (pluginFilter && plugin.name !== pluginFilter && pluginKey !== pluginFilter) continue;
3080
+ if (verbose) {
3081
+ getLogger().info(`
3082
+ 📦 Processing plugin: ${plugin.name}`);
3083
+ }
3084
+ const { manifest } = plugin;
3085
+ const pluginRelPath = path.relative(projectRoot, plugin.pluginPath);
3086
+ if (!pluginsIndexMap.has(plugin.name)) {
3087
+ const entry = {
3088
+ path: "./" + pluginRelPath.replace(/\\/g, "/"),
3089
+ contracts: [],
3090
+ actions: []
3091
+ };
3092
+ if (manifest.services?.length) {
3093
+ entry.services = manifest.services.map((s2) => {
3094
+ const docPath = s2.doc ? "./" + path.relative(projectRoot, path.resolve(plugin.pluginPath, s2.doc)) : void 0;
3095
+ return {
3096
+ name: s2.name,
3097
+ marker: s2.marker,
3098
+ ...s2.description && { description: s2.description },
3099
+ ...docPath && { doc: docPath }
3100
+ };
3101
+ });
3102
+ }
3103
+ if (manifest.contexts?.length) {
3104
+ entry.contexts = manifest.contexts.map((c2) => {
3105
+ const docPath = c2.doc ? "./" + path.relative(projectRoot, path.resolve(plugin.pluginPath, c2.doc)) : void 0;
3106
+ return {
3107
+ name: c2.name,
3108
+ marker: c2.marker,
3109
+ ...c2.description && { description: c2.description },
3110
+ ...docPath && { doc: docPath }
3111
+ };
3112
+ });
3113
+ }
3114
+ if (manifest.routes?.length) {
3115
+ entry.routes = manifest.routes.map((r) => ({
3116
+ path: r.path,
3117
+ ...r.description && { description: r.description }
3118
+ }));
3119
+ }
3120
+ if (manifest.commands?.length) {
3121
+ entry.commands = manifest.commands.map((c2) => {
3122
+ let description;
3123
+ if (c2.command) {
3124
+ try {
3125
+ const cmdPath = path.resolve(plugin.pluginPath, c2.command);
3126
+ const cmdContent = fs.readFileSync(cmdPath, "utf-8");
3127
+ const parsed = YAML.parse(cmdContent);
3128
+ description = parsed?.description;
3129
+ } catch {
3130
+ }
3131
+ }
3132
+ return {
3133
+ name: c2.name,
3134
+ ...description && { description }
3135
+ };
3136
+ });
3137
+ }
3138
+ pluginsIndexMap.set(plugin.name, entry);
3139
+ }
3140
+ if (!dynamicOnly && manifest.contracts) {
3141
+ for (const contract of manifest.contracts) {
3142
+ const contractPath = resolveStaticContractPath(
3143
+ plugin,
3144
+ contract.contract,
3145
+ projectRoot
3146
+ );
3147
+ const relativePath = path.relative(projectRoot, contractPath);
3148
+ let description = contract.description;
3149
+ if (!description) {
3150
+ try {
3151
+ const contractContent = fs.readFileSync(contractPath, "utf-8");
3152
+ const parsed = YAML.parse(contractContent);
3153
+ if (parsed?.description && typeof parsed.description === "string") {
3154
+ description = parsed.description;
3155
+ }
3156
+ } catch {
3157
+ }
3158
+ }
3159
+ pluginsIndexMap.get(plugin.name).contracts.push({
3160
+ name: contract.name,
3161
+ ...description && { description },
3162
+ type: "static",
3163
+ path: "./" + relativePath
3164
+ });
3165
+ staticCount++;
3166
+ if (verbose) {
3167
+ getLogger().info(` 📄 Static: ${contract.name}`);
3168
+ }
3169
+ }
3170
+ }
3171
+ if (manifest.dynamic_contracts) {
3172
+ const dynamicConfigs = Array.isArray(manifest.dynamic_contracts) ? manifest.dynamic_contracts : [manifest.dynamic_contracts];
3173
+ const pluginOutputDir = path.join(outputDir, plugin.name.replace(/[@/]/g, "_"));
3174
+ fs.mkdirSync(pluginOutputDir, { recursive: true });
3175
+ for (const config of dynamicConfigs) {
3176
+ if (verbose) {
3177
+ getLogger().info(` ⚡ Dynamic contracts (prefix: ${config.prefix})`);
3178
+ }
3179
+ try {
3180
+ const generatedContracts = await executeDynamicGenerator(
3181
+ plugin,
3182
+ config,
3183
+ projectRoot,
3184
+ services,
3185
+ verbose,
3186
+ viteServer
3187
+ );
3188
+ const prefix = config.prefix;
3189
+ if (generatedContracts.length > 1) {
3190
+ const missing = generatedContracts.filter((c2) => !c2.name);
3191
+ if (missing.length > 0) {
3192
+ getLogger().error(
3193
+ ` ❌ Dynamic contract generator for "${prefix}" returned ${generatedContracts.length} contracts but ${missing.length} are missing a name. Names are required when a generator returns multiple contracts.`
3194
+ );
3195
+ continue;
3196
+ }
3197
+ }
3198
+ for (const generated of generatedContracts) {
3199
+ const kebabName = generated.name ? toKebabCase(generated.name) : null;
3200
+ const fullName = kebabName ? `${prefix}/${kebabName}` : prefix;
3201
+ const fileName = kebabName ? `${prefix}-${kebabName}.jay-contract` : `${prefix}.jay-contract`;
3202
+ const filePath = path.join(pluginOutputDir, fileName);
3203
+ fs.writeFileSync(filePath, generated.yaml, "utf-8");
3204
+ const relativePath = path.relative(projectRoot, filePath);
3205
+ let dynDescription;
3206
+ try {
3207
+ const parsedYaml = YAML.parse(generated.yaml);
3208
+ if (parsedYaml?.description && typeof parsedYaml.description === "string") {
3209
+ dynDescription = parsedYaml.description;
3210
+ }
3211
+ } catch {
3212
+ }
3213
+ const contractEntry = {
3214
+ name: fullName,
3215
+ ...dynDescription && { description: dynDescription },
3216
+ type: "dynamic",
3217
+ path: "./" + relativePath,
3218
+ ...generated.metadata && { metadata: generated.metadata }
3219
+ };
3220
+ pluginsIndexMap.get(plugin.name).contracts.push(contractEntry);
3221
+ dynamicCount++;
3222
+ if (verbose) {
3223
+ getLogger().info(` ⚡ Materialized: ${fullName}`);
3224
+ }
3225
+ }
3226
+ } catch (error) {
3227
+ getLogger().error(
3228
+ ` ❌ Failed to materialize dynamic contracts for ${plugin.name} (${config.prefix}): ${error}`
3229
+ );
3230
+ }
3231
+ }
3232
+ }
3233
+ if (manifest.actions && Array.isArray(manifest.actions)) {
3234
+ for (const entry of manifest.actions) {
3235
+ const { name: actionName, action: actionPath } = normalizeActionEntry(entry);
3236
+ if (!actionPath) continue;
3237
+ const metadataFilePath = resolveActionFilePath(
3238
+ actionPath,
3239
+ plugin.packageName,
3240
+ plugin.pluginPath,
3241
+ plugin.isLocal,
3242
+ projectRoot
3243
+ );
3244
+ if (!metadataFilePath) continue;
3245
+ const metadata = loadActionMetadata(metadataFilePath);
3246
+ if (!metadata) continue;
3247
+ const actionRelPath = path.relative(projectRoot, metadataFilePath);
3248
+ const pluginEntry = pluginsIndexMap.get(plugin.name);
3249
+ if (!pluginEntry.actions) pluginEntry.actions = [];
3250
+ pluginEntry.actions.push({
3251
+ name: metadata.name,
3252
+ description: metadata.description,
3253
+ path: "./" + actionRelPath.replace(/\\/g, "/")
3254
+ });
3255
+ if (verbose) {
3256
+ getLogger().info(` 🔧 Action: ${metadata.name} (${actionPath})`);
3257
+ }
3258
+ }
3259
+ }
3260
+ }
3261
+ const pluginsIndex = {
3262
+ plugins: Array.from(pluginsIndexMap.entries()).map(([name, data]) => ({
3263
+ name,
3264
+ path: data.path,
3265
+ contracts: data.contracts,
3266
+ ...data.actions && data.actions.length > 0 && { actions: data.actions },
3267
+ ...data.services?.length && { services: data.services },
3268
+ ...data.contexts?.length && { contexts: data.contexts },
3269
+ ...data.routes?.length && { routes: data.routes },
3270
+ ...data.commands?.length && { commands: data.commands }
3271
+ }))
3272
+ };
3273
+ fs.mkdirSync(outputDir, { recursive: true });
3274
+ const agentKitDir = path.dirname(outputDir);
3275
+ const pluginsIndexPath = path.join(agentKitDir, "plugins-index.yaml");
3276
+ fs.writeFileSync(pluginsIndexPath, YAML.stringify(pluginsIndex), "utf-8");
3277
+ if (verbose) {
3278
+ getLogger().info(`
3279
+ ✅ Plugins index written to: ${pluginsIndexPath}`);
3280
+ }
3281
+ return {
3282
+ pluginsIndex,
3283
+ staticCount,
3284
+ dynamicCount,
3285
+ outputDir
3286
+ };
3287
+ }
3288
+ const CACHE_TAG_START = '<script type="application/jay-cache">';
3289
+ const CACHE_TAG_END = "<\/script>";
3290
+ function hashParams(params) {
3291
+ const sortedParams = Object.keys(params).sort().reduce(
3292
+ (acc, key) => {
3293
+ acc[key] = params[key];
3294
+ return acc;
3295
+ },
3296
+ {}
3297
+ );
3298
+ const json = JSON.stringify(sortedParams);
3299
+ if (json === "{}") return "";
3300
+ return "_" + crypto.createHash("md5").update(json).digest("hex").substring(0, 8);
3301
+ }
3302
+ function embedCacheMetadata(jayHtmlContent, slowViewState, carryForward, sourcePath) {
3303
+ const metadata = JSON.stringify({ slowViewState, carryForward, sourcePath });
3304
+ const cacheTag = `${CACHE_TAG_START}${metadata}${CACHE_TAG_END}`;
3305
+ const headMatch = jayHtmlContent.match(/<head[^>]*>/i);
3306
+ if (headMatch) {
3307
+ const insertPos = headMatch.index + headMatch[0].length;
3308
+ return jayHtmlContent.substring(0, insertPos) + "\n" + cacheTag + jayHtmlContent.substring(insertPos);
3309
+ }
3310
+ return `${cacheTag}
3311
+ ${jayHtmlContent}`;
3312
+ }
3313
+ function extractCacheMetadata(fileContent) {
3314
+ const startIdx = fileContent.indexOf(CACHE_TAG_START);
3315
+ if (startIdx === -1) return void 0;
3316
+ const jsonStart = startIdx + CACHE_TAG_START.length;
3317
+ const endIdx = fileContent.indexOf(CACHE_TAG_END, jsonStart);
3318
+ if (endIdx === -1) return void 0;
3319
+ const jsonStr = fileContent.substring(jsonStart, endIdx);
3320
+ const metadata = JSON.parse(jsonStr);
3321
+ const tagEnd = endIdx + CACHE_TAG_END.length;
3322
+ const afterTag = fileContent[tagEnd] === "\n" ? tagEnd + 1 : tagEnd;
3323
+ const content = fileContent.substring(0, startIdx) + fileContent.substring(afterTag);
3324
+ return {
3325
+ content,
3326
+ slowViewState: metadata.slowViewState,
3327
+ carryForward: metadata.carryForward,
3328
+ sourcePath: metadata.sourcePath
3329
+ };
3330
+ }
3331
+ class SlowRenderCache {
3332
+ /**
3333
+ * @param cacheDir - Directory where pre-rendered jay-html files are stored
3334
+ * @param pagesRoot - Root directory of the pages (for relative path calculation)
3335
+ */
3336
+ constructor(cacheDir, pagesRoot) {
3337
+ __publicField2(this, "pathToFiles", /* @__PURE__ */ new Map());
3338
+ __publicField2(this, "cacheDir");
3339
+ __publicField2(this, "pagesRoot");
3340
+ this.cacheDir = cacheDir;
3341
+ this.pagesRoot = pagesRoot;
3342
+ }
3343
+ /**
3344
+ * Get a cached pre-rendered jay-html entry by reading from disk.
3345
+ * Returns undefined if the cache file doesn't exist or has no metadata tag.
3346
+ */
3347
+ async get(jayHtmlPath, params, routeDir) {
3348
+ const preRenderedPath = this.computeCachePath(jayHtmlPath, params, routeDir);
3349
+ let fileContent;
3350
+ try {
3351
+ fileContent = await fs__default.readFile(preRenderedPath, "utf-8");
3352
+ } catch {
3353
+ return void 0;
3354
+ }
3355
+ const extracted = extractCacheMetadata(fileContent);
3356
+ if (!extracted) return void 0;
3357
+ this.trackFile(jayHtmlPath, preRenderedPath);
3358
+ return {
3359
+ preRenderedPath,
3360
+ preRenderedContent: extracted.content,
3361
+ slowViewState: extracted.slowViewState,
3362
+ carryForward: extracted.carryForward,
3363
+ sourcePath: extracted.sourcePath
3364
+ };
3365
+ }
3366
+ /**
3367
+ * Store a pre-rendered jay-html entry.
3368
+ * Embeds metadata as a <script> tag and writes to disk.
3369
+ * Returns the full cache entry with stripped content.
3370
+ */
3371
+ async set(jayHtmlPath, params, preRenderedJayHtml, slowViewState, carryForward, routeDir) {
3372
+ const preRenderedPath = this.computeCachePath(jayHtmlPath, params, routeDir);
3373
+ const fileContent = embedCacheMetadata(
3374
+ preRenderedJayHtml,
3375
+ slowViewState,
3376
+ carryForward,
3377
+ jayHtmlPath
3378
+ );
3379
+ await fs__default.mkdir(path__default.dirname(preRenderedPath), { recursive: true });
3380
+ await fs__default.writeFile(preRenderedPath, fileContent, "utf-8");
3381
+ this.trackFile(jayHtmlPath, preRenderedPath);
3382
+ return {
3383
+ preRenderedPath,
3384
+ preRenderedContent: preRenderedJayHtml,
3385
+ slowViewState,
3386
+ carryForward,
3387
+ sourcePath: jayHtmlPath
3388
+ };
3389
+ }
3390
+ /**
3391
+ * Check if a pre-rendered entry exists for the given path and params
3392
+ */
3393
+ async has(jayHtmlPath, params, routeDir) {
3394
+ const preRenderedPath = this.computeCachePath(jayHtmlPath, params, routeDir);
3395
+ try {
3396
+ await fs__default.access(preRenderedPath);
3397
+ return true;
3398
+ } catch {
3399
+ return false;
3400
+ }
3401
+ }
3402
+ /**
3403
+ * Invalidate all cached entries for a given jay-html source path.
3404
+ * Deletes cached files from disk.
3405
+ */
3406
+ async invalidate(jayHtmlPath) {
3407
+ const files = this.pathToFiles.get(jayHtmlPath);
3408
+ if (files) {
3409
+ for (const filePath of files) {
3410
+ try {
3411
+ await fs__default.unlink(filePath);
3412
+ } catch {
3413
+ }
3414
+ }
3415
+ this.pathToFiles.delete(jayHtmlPath);
3416
+ }
3417
+ await this.scanAndDeleteCacheFiles(jayHtmlPath);
3418
+ }
3419
+ /**
3420
+ * Invalidate all entries that depend on a changed file.
3421
+ * The changedPath could be:
3422
+ * - A jay-html file itself
3423
+ * - A component file (page.ts)
3424
+ * - Any other dependency
3425
+ *
3426
+ * @param changedPath - Absolute path to the changed file
3427
+ * @param resolveDependencies - Optional function to resolve which jay-html files depend on the changed file
3428
+ */
3429
+ async invalidateByDependency(changedPath, resolveDependencies) {
3430
+ if (changedPath.endsWith(".jay-html")) {
3431
+ await this.invalidate(changedPath);
3432
+ return;
3433
+ }
3434
+ if (resolveDependencies) {
3435
+ const dependentPaths = resolveDependencies(changedPath);
3436
+ for (const depPath of dependentPaths) {
3437
+ await this.invalidate(depPath);
3438
+ }
3439
+ }
3440
+ }
3441
+ /**
3442
+ * Clear all cached entries and delete cached files from disk
3443
+ */
3444
+ async clear() {
3445
+ for (const files of this.pathToFiles.values()) {
3446
+ for (const filePath of files) {
3447
+ try {
3448
+ await fs__default.unlink(filePath);
3449
+ } catch {
3450
+ }
3451
+ }
3452
+ }
3453
+ this.pathToFiles.clear();
3454
+ try {
3455
+ await fs__default.rm(this.cacheDir, { recursive: true, force: true });
3456
+ } catch {
3457
+ }
3458
+ }
3459
+ /**
3460
+ * Get all cached jay-html paths (for debugging/monitoring)
3461
+ */
3462
+ getCachedPaths() {
3463
+ return Array.from(this.pathToFiles.keys());
3464
+ }
3465
+ /**
3466
+ * Compute the cache file path for a given jay-html path and params.
3467
+ */
3468
+ computeCachePath(jayHtmlPath, params, routeDir) {
3469
+ let dir;
3470
+ if (routeDir) {
3471
+ dir = routeDir;
3472
+ } else {
3473
+ const relativePath = path__default.relative(this.pagesRoot, jayHtmlPath);
3474
+ dir = relativePath.startsWith("..") ? path__default.join(
3475
+ "_plugins",
3476
+ crypto.createHash("md5").update(jayHtmlPath).digest("hex").slice(0, 12)
3477
+ ) : path__default.dirname(relativePath);
3478
+ }
3479
+ const basename = path__default.basename(jayHtmlPath, ".jay-html");
3480
+ const paramsHash = hashParams(params);
3481
+ const cacheFileName = `${basename}${paramsHash}.jay-html`;
3482
+ return path__default.join(this.cacheDir, dir, cacheFileName);
3483
+ }
3484
+ /**
3485
+ * Track a pre-rendered file path for invalidation.
3486
+ */
3487
+ trackFile(jayHtmlPath, preRenderedPath) {
3488
+ if (!this.pathToFiles.has(jayHtmlPath)) {
3489
+ this.pathToFiles.set(jayHtmlPath, /* @__PURE__ */ new Set());
3490
+ }
3491
+ this.pathToFiles.get(jayHtmlPath).add(preRenderedPath);
3492
+ }
3493
+ /**
3494
+ * Scan the cache directory for files matching a route and delete them.
3495
+ * Handles the startup case where pathToFiles is not populated from a previous session.
3496
+ */
3497
+ async scanAndDeleteCacheFiles(jayHtmlPath) {
3498
+ const relativePath = path__default.relative(this.pagesRoot, jayHtmlPath);
3499
+ const dir = relativePath.startsWith("..") ? path__default.join(
3500
+ "_plugins",
3501
+ crypto.createHash("md5").update(jayHtmlPath).digest("hex").slice(0, 12)
3502
+ ) : path__default.dirname(relativePath);
3503
+ const basename = path__default.basename(jayHtmlPath, ".jay-html");
3504
+ const cacheSubDir = path__default.join(this.cacheDir, dir);
3505
+ try {
3506
+ const files = await fs__default.readdir(cacheSubDir);
3507
+ for (const file of files) {
3508
+ if (file.startsWith(basename)) {
3509
+ try {
3510
+ await fs__default.unlink(path__default.join(cacheSubDir, file));
3511
+ } catch {
3512
+ }
3513
+ }
3514
+ }
3515
+ } catch {
3516
+ }
3517
+ }
3518
+ }
3519
+ class ServiceLifecycleManager {
3520
+ constructor(projectRoot, sourceBase = "src") {
3521
+ /** Path to project's lib/init.ts (makeJayInit pattern) */
3522
+ __publicField(this, "projectInitFilePath", null);
3523
+ __publicField(this, "isInitialized", false);
3524
+ __publicField(this, "viteServer", null);
3525
+ __publicField(this, "pluginsWithInit", []);
3526
+ this.projectRoot = projectRoot;
3527
+ this.sourceBase = sourceBase;
3528
+ }
3529
+ /**
3530
+ * Set the Vite server instance for SSR module loading
3531
+ */
3532
+ setViteServer(viteServer) {
3533
+ this.viteServer = viteServer;
3534
+ }
3535
+ /**
3536
+ * Finds the project init file using makeJayInit pattern.
3537
+ * Looks in: {projectRoot}/{sourceBase}/init.{ts,js}
3538
+ */
3539
+ findProjectInitFile() {
3540
+ const extensions = [".ts", ".js"];
3541
+ for (const ext of extensions) {
3542
+ const filePath = path.join(this.projectRoot, this.sourceBase, "init" + ext);
3543
+ if (fs.existsSync(filePath)) {
3544
+ return filePath;
3545
+ }
3546
+ }
3547
+ return null;
3548
+ }
3549
+ /**
3550
+ * Initializes services by:
3551
+ * 1. Discovering and executing plugin server inits (in dependency order)
3552
+ * 2. Loading and executing project lib/init.ts
3553
+ * 3. Running all registered onInit callbacks
3554
+ * 4. Auto-discovering and registering actions
3555
+ */
3556
+ async initialize() {
3557
+ const log = getLogger();
3558
+ if (this.isInitialized) {
3559
+ log.warn("[Services] Already initialized, skipping...");
3560
+ return;
3561
+ }
3562
+ this.projectInitFilePath = this.findProjectInitFile();
3563
+ const discoveredPlugins = await discoverPluginsWithInit({
3564
+ projectRoot: this.projectRoot,
3565
+ verbose: true
3566
+ });
3567
+ this.pluginsWithInit = sortPluginsByDependencies(discoveredPlugins);
3568
+ if (this.pluginsWithInit.length > 0) {
3569
+ log.info(
3570
+ `[Services] Found ${this.pluginsWithInit.length} plugin(s) with init: ${this.pluginsWithInit.map((p) => p.name).join(", ")}`
3571
+ );
3572
+ }
3573
+ await executePluginServerInits(this.pluginsWithInit, this.viteServer ?? void 0, true);
3574
+ if (this.projectInitFilePath) {
3575
+ log.info("[DevServer] Loading project init: src/init.ts");
3576
+ try {
3577
+ if (this.viteServer) {
3578
+ const module = await this.viteServer.ssrLoadModule(this.projectInitFilePath);
3579
+ if (module.init?._serverInit) {
3580
+ log.info("[DevServer] Running server init: project");
3581
+ const { setClientInitData } = await import("@jay-framework/stack-server-runtime");
3582
+ const clientData = await module.init._serverInit();
3583
+ if (clientData !== void 0 && clientData !== null) {
3584
+ setClientInitData("project", clientData);
3585
+ }
3586
+ }
3587
+ } else {
3588
+ const fileUrl = pathToFileURL(this.projectInitFilePath).href;
3589
+ await import(fileUrl);
3590
+ }
3591
+ } catch (error) {
3592
+ log.error(`[Services] Failed to load project init: ${error}`);
3593
+ throw error;
3594
+ }
3595
+ } else {
3596
+ log.info("[Services] No init.ts found, skipping project initialization");
3597
+ }
3598
+ await runInitCallbacks();
3599
+ log.info("[Services] Initialization complete");
3600
+ await this.discoverActions();
3601
+ this.isInitialized = true;
3602
+ }
3603
+ /**
3604
+ * Auto-discovers and registers actions from project and plugins.
3605
+ */
3606
+ async discoverActions() {
3607
+ const log = getLogger();
3608
+ let totalActions = 0;
3609
+ try {
3610
+ const result = await discoverAndRegisterActions({
3611
+ projectRoot: this.projectRoot,
3612
+ actionsDir: path.join(this.sourceBase, "actions"),
3613
+ registry: actionRegistry,
3614
+ verbose: true,
3615
+ viteServer: this.viteServer ?? void 0
3616
+ });
3617
+ totalActions += result.actionCount;
3618
+ } catch (error) {
3619
+ log.error(`[Actions] Failed to auto-discover project actions: ${error}`);
3620
+ }
3621
+ try {
3622
+ const pluginActions = await discoverAllPluginActions({
3623
+ projectRoot: this.projectRoot,
3624
+ registry: actionRegistry,
3625
+ verbose: true,
3626
+ viteServer: this.viteServer ?? void 0
3627
+ });
3628
+ totalActions += pluginActions.length;
3629
+ } catch (error) {
3630
+ log.error(`[Actions] Failed to auto-discover plugin actions: ${error}`);
3631
+ }
3632
+ if (totalActions > 0) {
3633
+ log.info(`[Actions] Auto-registered ${totalActions} action(s) total`);
3634
+ }
3635
+ }
3636
+ /**
3637
+ * Shuts down services gracefully with timeout
3638
+ */
3639
+ async shutdown(timeoutMs = 5e3) {
3640
+ const log = getLogger();
3641
+ if (!this.isInitialized) {
3642
+ return;
3643
+ }
3644
+ log.info("[Services] Shutting down...");
3645
+ try {
3646
+ await Promise.race([
3647
+ runShutdownCallbacks(),
3648
+ new Promise(
3649
+ (_, reject) => setTimeout(() => reject(new Error("Shutdown timeout")), timeoutMs)
3650
+ )
3651
+ ]);
3652
+ log.info("[Services] Shutdown complete");
3653
+ } catch (error) {
3654
+ if (error.message === "Shutdown timeout") {
3655
+ log.warn(`[Services] Shutdown timed out after ${timeoutMs}ms`);
3656
+ } else {
3657
+ log.error(`[Services] Shutdown error: ${error}`);
3658
+ }
3659
+ } finally {
3660
+ this.isInitialized = false;
3661
+ }
3662
+ }
3663
+ /**
3664
+ * Hot reload: shutdown, clear caches, re-import, and re-initialize
3665
+ */
3666
+ async reload() {
3667
+ const log = getLogger();
3668
+ log.info("[Services] Reloading services...");
3669
+ await this.shutdown();
3670
+ clearLifecycleCallbacks();
3671
+ clearServiceRegistry();
3672
+ clearClientInitData();
3673
+ actionRegistry.clear();
3674
+ if (this.projectInitFilePath && this.viteServer) {
3675
+ const moduleNode = this.viteServer.moduleGraph.getModuleById(this.projectInitFilePath);
3676
+ if (moduleNode) {
3677
+ await this.viteServer.moduleGraph.invalidateModule(moduleNode);
3678
+ }
3679
+ } else if (this.projectInitFilePath) {
3680
+ delete require.cache[require.resolve(this.projectInitFilePath)];
3681
+ }
3682
+ this.isInitialized = false;
3683
+ await this.initialize();
3684
+ log.info("[Services] Reload complete");
3685
+ }
3686
+ /**
3687
+ * Returns the path to the init file if found.
3688
+ */
3689
+ getInitFilePath() {
3690
+ return this.projectInitFilePath;
3691
+ }
3692
+ /**
3693
+ * Returns project init info for client-side embedding.
3694
+ */
3695
+ getProjectInit() {
3696
+ if (!this.projectInitFilePath) {
3697
+ return null;
3698
+ }
3699
+ return {
3700
+ importPath: this.projectInitFilePath,
3701
+ initExport: "init"
3702
+ };
3703
+ }
3704
+ /**
3705
+ * Returns the discovered plugins with init configurations.
3706
+ * Sorted by dependencies (plugins with no deps first).
3707
+ */
3708
+ getPluginsWithInit() {
3709
+ return this.pluginsWithInit;
3710
+ }
3711
+ /**
3712
+ * Checks if services are initialized
3713
+ */
3714
+ isReady() {
3715
+ return this.isInitialized;
3716
+ }
3717
+ }
3718
+ function deepMergeViewStates(base, overlay, trackByMap, path2 = "") {
3719
+ if (!base && !overlay) return {};
3720
+ if (!base) return overlay || {};
3721
+ if (!overlay) return base || {};
3722
+ const result = {};
3723
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(overlay)]);
3724
+ for (const key of allKeys) {
3725
+ const baseValue = base[key];
3726
+ const overlayValue = overlay[key];
3727
+ const currentPath = path2 ? `${path2}.${key}` : key;
3728
+ if (overlayValue === void 0) {
3729
+ result[key] = baseValue;
3730
+ } else if (baseValue === void 0) {
3731
+ result[key] = overlayValue;
3732
+ } else if (Array.isArray(baseValue) && Array.isArray(overlayValue)) {
3733
+ const trackByField = trackByMap[currentPath];
3734
+ if (trackByField) {
3735
+ result[key] = mergeArraysByTrackBy(
3736
+ baseValue,
1831
3737
  overlayValue,
1832
3738
  trackByField,
1833
3739
  trackByMap,
@@ -2028,7 +3934,7 @@ function mergeDottedMultipartKeys(body) {
2028
3934
  }
2029
3935
  function parseMultipart(req, tempDir) {
2030
3936
  return new Promise((resolve, reject) => {
2031
- fs$1.mkdirSync(tempDir, { recursive: true });
3937
+ fs$2.mkdirSync(tempDir, { recursive: true });
2032
3938
  const files = {};
2033
3939
  let jsonData = {};
2034
3940
  let fileCount = 0;
@@ -2043,7 +3949,7 @@ function parseMultipart(req, tempDir) {
2043
3949
  const filename = info.filename || `upload-${fileCount}`;
2044
3950
  const tempPath = path$1.join(tempDir, `${fileCount}-${filename}`);
2045
3951
  let size = 0;
2046
- const writeStream = fs$1.createWriteStream(tempPath);
3952
+ const writeStream = fs$2.createWriteStream(tempPath);
2047
3953
  stream.pipe(writeStream);
2048
3954
  stream.on("data", (data) => {
2049
3955
  size += data.length;
@@ -2102,7 +4008,7 @@ function parseMultipart(req, tempDir) {
2102
4008
  }
2103
4009
  function cleanupTempDir(dir) {
2104
4010
  try {
2105
- fs$1.rmSync(dir, { recursive: true, force: true });
4011
+ fs$2.rmSync(dir, { recursive: true, force: true });
2106
4012
  } catch {
2107
4013
  }
2108
4014
  }
@@ -2133,7 +4039,7 @@ function actionBodyParser(options) {
2133
4039
  });
2134
4040
  return;
2135
4041
  }
2136
- const requestId = crypto.randomUUID();
4042
+ const requestId = crypto$1.randomUUID();
2137
4043
  const tempDir = path$1.join(buildFolder, ".tmp", "actions", requestId);
2138
4044
  parseMultipart(req, tempDir).then(({ body: body2, tempDir: td }) => {
2139
4045
  req.body = body2;
@@ -2180,7 +4086,7 @@ class FreezeStore {
2180
4086
  this.dir = path.join(buildFolder, "freezes");
2181
4087
  }
2182
4088
  async save(route, viewState, routePattern) {
2183
- await fs.mkdir(this.dir, { recursive: true });
4089
+ await fs$1.mkdir(this.dir, { recursive: true });
2184
4090
  const entry = {
2185
4091
  id: randomUUID().slice(0, 8),
2186
4092
  route,
@@ -2188,7 +4094,7 @@ class FreezeStore {
2188
4094
  viewState,
2189
4095
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
2190
4096
  };
2191
- await fs.writeFile(
4097
+ await fs$1.writeFile(
2192
4098
  path.join(this.dir, `${entry.id}.json`),
2193
4099
  JSON.stringify(entry, null, 2),
2194
4100
  "utf-8"
@@ -2197,7 +4103,7 @@ class FreezeStore {
2197
4103
  }
2198
4104
  async get(id) {
2199
4105
  try {
2200
- const content = await fs.readFile(path.join(this.dir, `${id}.json`), "utf-8");
4106
+ const content = await fs$1.readFile(path.join(this.dir, `${id}.json`), "utf-8");
2201
4107
  return JSON.parse(content);
2202
4108
  } catch {
2203
4109
  return void 0;
@@ -2205,12 +4111,12 @@ class FreezeStore {
2205
4111
  }
2206
4112
  async list(route) {
2207
4113
  try {
2208
- const files = await fs.readdir(this.dir);
4114
+ const files = await fs$1.readdir(this.dir);
2209
4115
  const entries = [];
2210
4116
  for (const file of files) {
2211
4117
  if (!file.endsWith(".json")) continue;
2212
4118
  try {
2213
- const content = await fs.readFile(path.join(this.dir, file), "utf-8");
4119
+ const content = await fs$1.readFile(path.join(this.dir, file), "utf-8");
2214
4120
  const entry = JSON.parse(content);
2215
4121
  if (!route || entry.routePattern === route || entry.route === route) {
2216
4122
  entries.push(entry);
@@ -2229,7 +4135,7 @@ class FreezeStore {
2229
4135
  const entry = await this.get(id);
2230
4136
  if (!entry) return false;
2231
4137
  entry.name = name;
2232
- await fs.writeFile(
4138
+ await fs$1.writeFile(
2233
4139
  path.join(this.dir, `${id}.json`),
2234
4140
  JSON.stringify(entry, null, 2),
2235
4141
  "utf-8"
@@ -2238,7 +4144,7 @@ class FreezeStore {
2238
4144
  }
2239
4145
  async delete(id) {
2240
4146
  try {
2241
- await fs.unlink(path.join(this.dir, `${id}.json`));
4147
+ await fs$1.unlink(path.join(this.dir, `${id}.json`));
2242
4148
  return true;
2243
4149
  } catch {
2244
4150
  return false;
@@ -2343,7 +4249,7 @@ async function scanPluginRoutes(projectRoot, projectRoutes) {
2343
4249
  continue;
2344
4250
  }
2345
4251
  const isLocalComponent = route.component.startsWith(".");
2346
- const compPath = isLocalComponent ? path__default.resolve(plugin.pluginPath, route.component) : resolvePluginModule(plugin);
4252
+ const compPath = isLocalComponent ? path__default.resolve(plugin.pluginPath, route.component) : resolvePluginModule(plugin, route.devOnly === true ? "./tools" : ".");
2347
4253
  const componentExport = isLocalComponent ? void 0 : route.component;
2348
4254
  pluginRoutes.push(
2349
4255
  createRoute(route.path, jayHtmlPath, compPath, componentExport, {
@@ -2361,7 +4267,7 @@ function resolvePluginExport(pluginPath, exportSubpath) {
2361
4267
  const normalized = exportSubpath.replace(/^\.\//, "");
2362
4268
  const packageJsonPath = path__default.join(pluginPath, "package.json");
2363
4269
  try {
2364
- const packageJson = JSON.parse(fsSync__default.readFileSync(packageJsonPath, "utf-8"));
4270
+ const packageJson = JSON.parse(fs__default$1.readFileSync(packageJsonPath, "utf-8"));
2365
4271
  if (packageJson.exports) {
2366
4272
  const exportKey = "./" + normalized;
2367
4273
  const exportValue = packageJson.exports[exportKey];
@@ -2378,23 +4284,23 @@ function resolvePluginExport(pluginPath, exportSubpath) {
2378
4284
  for (const dir of ["dist", "lib", ""]) {
2379
4285
  const candidate = path__default.join(pluginPath, dir, normalized);
2380
4286
  try {
2381
- fsSync__default.accessSync(candidate);
4287
+ fs__default$1.accessSync(candidate);
2382
4288
  return candidate;
2383
4289
  } catch {
2384
4290
  }
2385
4291
  }
2386
4292
  return void 0;
2387
4293
  }
2388
- function resolvePluginModule(plugin) {
4294
+ function resolvePluginModule(plugin, exportKey = ".") {
2389
4295
  const pkgJsonPath = path__default.join(plugin.pluginPath, "package.json");
2390
- if (fsSync__default.existsSync(pkgJsonPath)) {
4296
+ if (fs__default$1.existsSync(pkgJsonPath)) {
2391
4297
  try {
2392
- const pkg = JSON.parse(fsSync__default.readFileSync(pkgJsonPath, "utf-8"));
2393
- const mainExport = pkg.exports?.["."];
4298
+ const pkg = JSON.parse(fs__default$1.readFileSync(pkgJsonPath, "utf-8"));
4299
+ const mainExport = pkg.exports?.[exportKey];
2394
4300
  const mainPath = typeof mainExport === "string" ? mainExport : mainExport?.default || mainExport?.import || pkg.main;
2395
4301
  if (mainPath) {
2396
4302
  const resolved = path__default.join(plugin.pluginPath, mainPath);
2397
- if (fsSync__default.existsSync(resolved)) return resolved;
4303
+ if (fs__default$1.existsSync(resolved)) return resolved;
2398
4304
  }
2399
4305
  } catch {
2400
4306
  }
@@ -2403,7 +4309,7 @@ function resolvePluginModule(plugin) {
2403
4309
  for (const ext of [".ts", ".js", "/index.ts", "/index.js"]) {
2404
4310
  const candidate = path__default.join(plugin.pluginPath, modulePath + ext);
2405
4311
  try {
2406
- fsSync__default.accessSync(candidate);
4312
+ fs__default$1.accessSync(candidate);
2407
4313
  return candidate;
2408
4314
  } catch {
2409
4315
  }
@@ -2411,7 +4317,7 @@ function resolvePluginModule(plugin) {
2411
4317
  for (const ext of [".ts", ".js"]) {
2412
4318
  const candidate = path__default.join(plugin.pluginPath, "lib", path__default.basename(modulePath) + ext);
2413
4319
  try {
2414
- fsSync__default.accessSync(candidate);
4320
+ fs__default$1.accessSync(candidate);
2415
4321
  return candidate;
2416
4322
  } catch {
2417
4323
  }
@@ -2507,6 +4413,7 @@ function mkRoute(route, vite, slowlyPhase, options, slowRenderCache, freezeStore
2507
4413
  pageParams,
2508
4414
  query["format"] === "fragment" ? "fragment" : "page",
2509
4415
  res,
4416
+ url,
2510
4417
  timing
2511
4418
  );
2512
4419
  return;
@@ -2713,9 +4620,7 @@ async function handlePreRenderRequest(vite, route, options, slowlyPhase, slowRen
2713
4620
  );
2714
4621
  if (renderedSlowly.kind !== "PhaseOutput") {
2715
4622
  timing?.recordSlowRender(Date.now() - slowStart);
2716
- if (renderedSlowly.kind === "ClientError") {
2717
- handleOtherResponseCodes(res, renderedSlowly);
2718
- }
4623
+ handleOtherResponseCodes(res, renderedSlowly);
2719
4624
  timing?.end();
2720
4625
  return;
2721
4626
  }
@@ -2825,9 +4730,7 @@ async function handleClientOnlyRequest(vite, route, options, slowlyPhase, pagePa
2825
4730
  );
2826
4731
  if (renderedSlowly.kind !== "PhaseOutput") {
2827
4732
  timing?.recordSlowRender(Date.now() - slowStart);
2828
- if (renderedSlowly.kind === "ClientError") {
2829
- handleOtherResponseCodes(res, renderedSlowly);
2830
- }
4733
+ handleOtherResponseCodes(res, renderedSlowly);
2831
4734
  timing?.end();
2832
4735
  return;
2833
4736
  }
@@ -2959,7 +4862,7 @@ async function sendResponse(vite, res, url, jayHtmlPath, sourceJayHtmlPath, page
2959
4862
  res.status(200).set({ "Content-Type": "text/html" }).send(compiledPageHtml);
2960
4863
  timing?.end();
2961
4864
  }
2962
- async function handleFrozenRequest(vite, route, options, freezeStore, slowRenderCache, freezeId, pageParams, format, res, timing) {
4865
+ async function handleFrozenRequest(vite, route, options, freezeStore, slowRenderCache, freezeId, pageParams, format, res, url, timing) {
2963
4866
  const entry = await freezeStore.get(freezeId);
2964
4867
  if (!entry) {
2965
4868
  getLogger().warn(`[Freeze] Freeze "${freezeId}" not found`);
@@ -2982,6 +4885,7 @@ async function handleFrozenRequest(vite, route, options, freezeStore, slowRender
2982
4885
  sourceDir,
2983
4886
  JAY_IMPORT_RESOLVER
2984
4887
  );
4888
+ const injectDevHmr = format === "page";
2985
4889
  const html = await generateFrozenPageHtml(
2986
4890
  vite,
2987
4891
  fullJayHtml,
@@ -2994,13 +4898,19 @@ async function handleFrozenRequest(vite, route, options, freezeStore, slowRender
2994
4898
  options.jayRollupConfig?.tsConfigFilePath,
2995
4899
  sourceDir,
2996
4900
  format,
2997
- entry.name
4901
+ entry.name,
4902
+ injectDevHmr ? { injectDevHmr: true } : void 0
2998
4903
  );
2999
4904
  const headers = { "Content-Type": "text/html" };
3000
4905
  if (format === "fragment") {
3001
4906
  headers["Access-Control-Allow-Origin"] = "*";
3002
4907
  }
3003
- res.status(200).set(headers).send(html);
4908
+ if (injectDevHmr) {
4909
+ const compiledHtml = await vite.transformIndexHtml(url || "/", html);
4910
+ res.status(200).set(headers).send(compiledHtml);
4911
+ } else {
4912
+ res.status(200).set(headers).send(html);
4913
+ }
3004
4914
  } catch (err) {
3005
4915
  getLogger().warn(`[Freeze] Failed to render frozen page: ${err.message}`);
3006
4916
  res.status(500).send(`Failed to render frozen page: ${err.message}`);
@@ -3184,7 +5094,7 @@ async function mkDevServer(rawOptions) {
3184
5094
  for (let i = devServerRoutes.length - 1; i >= 0; i--) {
3185
5095
  const route = devServerRoutes[i];
3186
5096
  const fsPath = route.fsRoute.jayHtmlPath;
3187
- const stillOnDisk = scannedRawRoutes.has(route.fsRoute.rawRoute) && fsSync__default.existsSync(fsPath);
5097
+ const stillOnDisk = scannedRawRoutes.has(route.fsRoute.rawRoute) && fs__default$1.existsSync(fsPath);
3188
5098
  if (!stillOnDisk) {
3189
5099
  devServerRoutes.splice(i, 1);
3190
5100
  }