@jay-framework/stack-server-runtime 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +131 -3
  2. package/dist/index.js +200 -13
  3. package/package.json +11 -11
package/dist/index.d.ts CHANGED
@@ -21,7 +21,15 @@ interface LoadedPageParts {
21
21
  /** NPM package names used on this page (for filtering plugin inits) */
22
22
  usedPackages: Set<string>;
23
23
  }
24
- declare function loadPageParts(vite: ViteDevServer, route: JayRoute, pagesBase: string, projectBase: string, jayRollupConfig: JayRollupConfig): Promise<WithValidations<LoadedPageParts>>;
24
+ interface LoadPagePartsOptions {
25
+ /**
26
+ * Path to pre-rendered jay-html file to use instead of the original.
27
+ * When provided, this file (with slow-phase bindings resolved) is read.
28
+ * Import resolution still uses the original jay-html's directory.
29
+ */
30
+ preRenderedPath?: string;
31
+ }
32
+ declare function loadPageParts(vite: ViteDevServer, route: JayRoute, pagesBase: string, projectBase: string, jayRollupConfig: JayRollupConfig, options?: LoadPagePartsOptions): Promise<WithValidations<LoadedPageParts>>;
25
33
 
26
34
  interface SlowlyChangingPhase {
27
35
  runSlowlyForPage(pageParams: object, pageProps: PageProps, parts: Array<DevServerPagePart>): Promise<AnySlowlyRenderResult>;
@@ -183,6 +191,33 @@ declare function executeAction<T = any>(actionName: string, input: unknown): Pro
183
191
  * @deprecated Use actionRegistry.getCacheHeaders() instead
184
192
  */
185
193
  declare function getActionCacheHeaders(actionName: string): string | undefined;
194
+ /**
195
+ * Executes an action directly with proper service injection.
196
+ * Use this when calling actions from backend code (e.g., render phases).
197
+ *
198
+ * Unlike calling the action directly (which bypasses service injection),
199
+ * this function resolves services and calls the handler correctly.
200
+ *
201
+ * @param action - The JayAction to execute (with definition metadata)
202
+ * @param input - The input data for the action
203
+ * @returns The action result (throws on error)
204
+ *
205
+ * @example
206
+ * ```typescript
207
+ * // In a render phase
208
+ * import { runAction } from '@jay-framework/stack-server-runtime';
209
+ * import { searchProducts } from '../actions/stores-actions';
210
+ *
211
+ * async function renderFastChanging(props, slowCarryForward, wixStores) {
212
+ * // ✅ Correct - services are injected
213
+ * const result = await runAction(searchProducts, { query: '', pageSize: 12 });
214
+ *
215
+ * // ❌ Wrong - services are NOT injected (passes empty array)
216
+ * // const result = await searchProducts({ query: '', pageSize: 12 });
217
+ * }
218
+ * ```
219
+ */
220
+ declare function runAction<I, O>(action: JayAction<I, O> & JayActionDefinition<I, O, any[]>, input: I): Promise<O>;
186
221
 
187
222
  /**
188
223
  * Action discovery and auto-registration for Jay Stack.
@@ -332,6 +367,7 @@ interface PluginInitDiscoveryOptions {
332
367
  * in `plugin.yaml` via the `init` property.
333
368
  *
334
369
  * Scans both local plugins (src/plugins/) and NPM plugins (node_modules/).
370
+ * Also discovers transitive plugin dependencies (plugins that depend on other plugins).
335
371
  */
336
372
  declare function discoverPluginsWithInit(options: PluginInitDiscoveryOptions): Promise<PluginWithInit[]>;
337
373
  /**
@@ -383,7 +419,20 @@ interface ProjectClientInitInfo {
383
419
  /** Export name for the JayInit constant (default: 'init') */
384
420
  initExport?: string;
385
421
  }
386
- declare function generateClientScript(defaultViewState: object, fastCarryForward: object, parts: DevServerPagePart[], jayHtmlPath: string, trackByMap?: TrackByMap, clientInitData?: Record<string, Record<string, any>>, projectInit?: ProjectClientInitInfo, pluginInits?: PluginClientInitInfo[]): string;
422
+ /**
423
+ * Options for client script generation.
424
+ */
425
+ interface GenerateClientScriptOptions {
426
+ /** Enable automation integration (default: true in dev mode) */
427
+ enableAutomation?: boolean;
428
+ /**
429
+ * Slow ViewState that was baked into the pre-rendered jay-html.
430
+ * When provided, this is merged with fastViewState for the automation API
431
+ * so that AI/automation tools can see the complete page state.
432
+ */
433
+ slowViewState?: object;
434
+ }
435
+ declare function generateClientScript(defaultViewState: object, fastCarryForward: object, parts: DevServerPagePart[], jayHtmlPath: string, trackByMap?: TrackByMap, clientInitData?: Record<string, Record<string, any>>, projectInit?: ProjectClientInitInfo, pluginInits?: PluginClientInitInfo[], options?: GenerateClientScriptOptions): string;
387
436
 
388
437
  /**
389
438
  * Service registry for Jay Stack server-side dependency injection.
@@ -549,4 +598,83 @@ declare function getClientInitDataForKey(key: string): Record<string, any>;
549
598
  */
550
599
  declare function clearClientInitData(): void;
551
600
 
552
- export { type ActionDiscoveryOptions, type ActionDiscoveryResult, type ActionErrorResponse, type ActionExecutionResult, ActionRegistry, type DevServerPagePart, DevSlowlyChangingPhase, type LoadedPageParts, type PluginActionDiscoveryOptions, type PluginClientInitInfo, type PluginInitDiscoveryOptions, type PluginWithInit, type ProjectClientInitInfo, type RegisteredAction, type SlowlyChangingPhase, type ViteSSRLoader, actionRegistry, clearActionRegistry, clearClientInitData, clearLifecycleCallbacks, clearServiceRegistry, discoverAllPluginActions, discoverAndRegisterActions, discoverPluginActions, discoverPluginsWithInit, executeAction, executePluginServerInits, generateClientScript, getActionCacheHeaders, getClientInitData, getClientInitDataForKey, getRegisteredAction, getRegisteredActionNames, getService, hasAction, hasService, loadPageParts, onInit, onShutdown, preparePluginClientInits, registerAction, registerService, renderFastChangingData, resolveServices, runInitCallbacks, runLoadParams, runShutdownCallbacks, runSlowlyChangingRender, setClientInitData, sortPluginsByDependencies };
601
+ /**
602
+ * Cache entry for pre-rendered jay-html
603
+ */
604
+ interface SlowRenderCacheEntry {
605
+ /** Path to the pre-rendered jay-html file on disk */
606
+ preRenderedPath: string;
607
+ /** Slow ViewState that was baked into the jay-html */
608
+ slowViewState: object;
609
+ /** CarryForward data from slow rendering (passed to fast phase) */
610
+ carryForward: object;
611
+ /** Timestamp when this entry was created */
612
+ createdAt: number;
613
+ /** Source jay-html path (for debugging) */
614
+ sourcePath: string;
615
+ }
616
+ /**
617
+ * Cache for pre-rendered jay-html files.
618
+ *
619
+ * This cache stores jay-html content that has been transformed with slow-phase
620
+ * data baked in. The key insight is that since slow ViewState is embedded directly
621
+ * into the jay-html, we don't need to pass it to the client - only fast and
622
+ * interactive ViewState is sent.
623
+ *
624
+ * Pre-rendered files are written to disk so Vite can pick them up and compile them.
625
+ */
626
+ declare class SlowRenderCache {
627
+ private cache;
628
+ private pathToKeys;
629
+ private readonly cacheDir;
630
+ private readonly pagesRoot;
631
+ /**
632
+ * @param cacheDir - Directory where pre-rendered jay-html files are stored
633
+ * @param pagesRoot - Root directory of the pages (for relative path calculation)
634
+ */
635
+ constructor(cacheDir: string, pagesRoot: string);
636
+ /**
637
+ * Get a cached pre-rendered jay-html entry
638
+ */
639
+ get(jayHtmlPath: string, params: Record<string, string>): SlowRenderCacheEntry | undefined;
640
+ /**
641
+ * Store a pre-rendered jay-html entry in the cache.
642
+ * Writes the pre-rendered content to disk and stores metadata in memory.
643
+ */
644
+ set(jayHtmlPath: string, params: Record<string, string>, preRenderedJayHtml: string, slowViewState: object, carryForward: object): Promise<string>;
645
+ /**
646
+ * Check if a pre-rendered entry exists for the given path and params
647
+ */
648
+ has(jayHtmlPath: string, params: Record<string, string>): boolean;
649
+ /**
650
+ * Invalidate all cached entries for a given jay-html source path.
651
+ * This is called when the source file changes.
652
+ * Also deletes the cached files from disk.
653
+ */
654
+ invalidate(jayHtmlPath: string): Promise<void>;
655
+ /**
656
+ * Invalidate all entries that depend on a changed file.
657
+ * The changedPath could be:
658
+ * - A jay-html file itself
659
+ * - A component file (page.ts)
660
+ * - Any other dependency
661
+ *
662
+ * @param changedPath - Absolute path to the changed file
663
+ * @param resolveDependencies - Optional function to resolve which jay-html files depend on the changed file
664
+ */
665
+ invalidateByDependency(changedPath: string, resolveDependencies?: (changedPath: string) => string[]): Promise<void>;
666
+ /**
667
+ * Clear all cached entries and delete cached files from disk
668
+ */
669
+ clear(): Promise<void>;
670
+ /**
671
+ * Get the number of cached entries
672
+ */
673
+ get size(): number;
674
+ /**
675
+ * Get all cached jay-html paths (for debugging/monitoring)
676
+ */
677
+ getCachedPaths(): string[];
678
+ }
679
+
680
+ export { type ActionDiscoveryOptions, type ActionDiscoveryResult, type ActionErrorResponse, type ActionExecutionResult, ActionRegistry, type DevServerPagePart, DevSlowlyChangingPhase, type GenerateClientScriptOptions, type LoadedPageParts, type PluginActionDiscoveryOptions, type PluginClientInitInfo, type PluginInitDiscoveryOptions, type PluginWithInit, type ProjectClientInitInfo, type RegisteredAction, SlowRenderCache, type SlowRenderCacheEntry, type SlowlyChangingPhase, type ViteSSRLoader, actionRegistry, clearActionRegistry, clearClientInitData, clearLifecycleCallbacks, clearServiceRegistry, discoverAllPluginActions, discoverAndRegisterActions, discoverPluginActions, discoverPluginsWithInit, executeAction, executePluginServerInits, generateClientScript, getActionCacheHeaders, getClientInitData, getClientInitDataForKey, getRegisteredAction, getRegisteredActionNames, getService, hasAction, hasService, loadPageParts, onInit, onShutdown, preparePluginClientInits, registerAction, registerService, renderFastChangingData, resolveServices, runAction, runInitCallbacks, runLoadParams, runShutdownCallbacks, runSlowlyChangingRender, setClientInitData, sortPluginsByDependencies };
package/dist/index.js CHANGED
@@ -17,6 +17,7 @@ import "js-beautify";
17
17
  import fs$2 from "fs";
18
18
  import path$1 from "path";
19
19
  import YAML from "yaml";
20
+ import crypto from "node:crypto";
20
21
  const serviceRegistry = /* @__PURE__ */ new Map();
21
22
  function registerService(marker, service) {
22
23
  serviceRegistry.set(marker, service);
@@ -158,7 +159,9 @@ async function renderFastChangingData(pageParams, pageProps, carryForward, parts
158
159
  }
159
160
  return Promise.resolve(phaseOutput(fastViewState, fastCarryForward));
160
161
  }
161
- function generateClientScript(defaultViewState, fastCarryForward, parts, jayHtmlPath, trackByMap = {}, clientInitData2 = {}, projectInit, pluginInits = []) {
162
+ function generateClientScript(defaultViewState, fastCarryForward, parts, jayHtmlPath, trackByMap = {}, clientInitData2 = {}, projectInit, pluginInits = [], options = {}) {
163
+ const { enableAutomation = true, slowViewState } = options;
164
+ const hasSlowViewState = slowViewState && Object.keys(slowViewState).length > 0;
162
165
  const imports = parts.length > 0 ? parts.map((part) => part.clientImport).join("\n") + "\n" : "";
163
166
  const compositeParts = parts.length > 0 ? `[
164
167
  ${parts.map((part) => " " + part.clientPart).join(",\n")}
@@ -187,6 +190,27 @@ ${parts.map((part) => " " + part.clientPart).join(",\n")}
187
190
  // Project client initialization
188
191
  ${projectInitCall}
189
192
  ` : "";
193
+ const automationImport = enableAutomation ? hasSlowViewState ? `import { wrapWithAutomation, AUTOMATION_CONTEXT } from "@jay-framework/runtime-automation";
194
+ import { registerGlobalContext } from "@jay-framework/runtime";
195
+ import { deepMergeViewStates } from "@jay-framework/view-state-merge";` : `import { wrapWithAutomation, AUTOMATION_CONTEXT } from "@jay-framework/runtime-automation";
196
+ import { registerGlobalContext } from "@jay-framework/runtime";` : "";
197
+ const slowViewStateDecl = enableAutomation && hasSlowViewState ? `const slowViewState = ${JSON.stringify(slowViewState)};` : "";
198
+ const automationWrap = enableAutomation ? hasSlowViewState ? `
199
+ // Wrap with automation for dev tooling
200
+ // Deep merge slow+fast ViewState so automation can see full page state
201
+ const fullViewState = deepMergeViewStates(slowViewState, {...viewState, ...fastCarryForward}, trackByMap);
202
+ const wrapped = wrapWithAutomation(instance, { initialViewState: fullViewState, trackByMap });
203
+ registerGlobalContext(AUTOMATION_CONTEXT, wrapped.automation);
204
+ window.__jay = window.__jay || {};
205
+ window.__jay.automation = wrapped.automation;
206
+ target.appendChild(wrapped.element.dom);` : `
207
+ // Wrap with automation for dev tooling
208
+ const wrapped = wrapWithAutomation(instance);
209
+ registerGlobalContext(AUTOMATION_CONTEXT, wrapped.automation);
210
+ window.__jay = window.__jay || {};
211
+ window.__jay.automation = wrapped.automation;
212
+ target.appendChild(wrapped.element.dom);` : `
213
+ target.appendChild(instance.element.dom);`;
190
214
  return `<!doctype html>
191
215
  <html lang="en">
192
216
  <head>
@@ -198,10 +222,11 @@ ${parts.map((part) => " " + part.clientPart).join(",\n")}
198
222
  <div id="target"></div>
199
223
  <script type="module">
200
224
  import {makeCompositeJayComponent} from "@jay-framework/stack-client-runtime";
225
+ ${automationImport}
201
226
  ${pluginClientInitImports}
202
227
  ${projectInitImport}
203
228
  import { render } from '${jayHtmlPath}';
204
- ${imports}
229
+ ${imports}${slowViewStateDecl}
205
230
  const viewState = ${JSON.stringify(defaultViewState)};
206
231
  const fastCarryForward = ${JSON.stringify(fastCarryForward)};
207
232
  const trackByMap = ${JSON.stringify(trackByMap)};
@@ -210,25 +235,25 @@ ${clientInitExecution}
210
235
  const pageComp = makeCompositeJayComponent(render, viewState, fastCarryForward, ${compositeParts}, trackByMap)
211
236
 
212
237
  const instance = pageComp({...viewState, ...fastCarryForward})
213
- target.appendChild(instance.element.dom);
238
+ ${automationWrap}
214
239
  <\/script>
215
240
  </body>
216
241
  </html>`;
217
242
  }
218
243
  const require$2 = createRequire(import.meta.url);
219
- async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfig) {
244
+ async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfig, options) {
220
245
  const exists = await fs$1.access(route.compPath, fs$1.constants.F_OK).then(() => true).catch(() => false);
221
246
  const parts = [];
222
247
  if (exists) {
223
248
  const pageComponent = (await vite.ssrLoadModule(route.compPath)).page;
224
249
  parts.push({
225
250
  compDefinition: pageComponent,
226
- // Client import uses client-only code (server code stripped)
227
251
  clientImport: `import {page} from '${route.compPath}'`,
228
- clientPart: `{comp: page.comp, contextMarkers: []}`
252
+ clientPart: `{comp: page.comp, contextMarkers: page.contexts || []}`
229
253
  });
230
254
  }
231
- const jayHtmlSource = (await fs$1.readFile(route.jayHtmlPath)).toString();
255
+ const jayHtmlFilePath = options?.preRenderedPath ?? route.jayHtmlPath;
256
+ const jayHtmlSource = (await fs$1.readFile(jayHtmlFilePath)).toString();
232
257
  const fileName = path__default.basename(route.jayHtmlPath);
233
258
  const dirName = path__default.dirname(route.jayHtmlPath);
234
259
  const jayHtmlWithValidations = await parseJayFile(
@@ -261,7 +286,7 @@ async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfi
261
286
  key,
262
287
  compDefinition,
263
288
  clientImport: `import {${name}} from '${clientModuleImport}'`,
264
- clientPart: `{comp: ${name}.comp, contextMarkers: [], key: '${headlessImport.key}'}`
289
+ clientPart: `{comp: ${name}.comp, contextMarkers: ${name}.contexts || [], key: '${headlessImport.key}'}`
265
290
  };
266
291
  parts.push(part);
267
292
  }
@@ -420,6 +445,10 @@ async function executeAction(actionName, input) {
420
445
  function getActionCacheHeaders(actionName) {
421
446
  return actionRegistry.getCacheHeaders(actionName);
422
447
  }
448
+ async function runAction(action, input) {
449
+ const services = resolveServices(action.services);
450
+ return action.handler(input, ...services);
451
+ }
423
452
  var __defProp2 = Object.defineProperty;
424
453
  var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
425
454
  var __publicField2 = (obj, key, value) => {
@@ -747,6 +776,7 @@ const require2 = createRequire$1(import.meta.url);
747
776
  async function discoverPluginsWithInit(options) {
748
777
  const { projectRoot, verbose = false } = options;
749
778
  const plugins = [];
779
+ const visitedPackages = /* @__PURE__ */ new Set();
750
780
  const localPluginsPath = path.join(projectRoot, "src/plugins");
751
781
  if (fs.existsSync(localPluginsPath)) {
752
782
  try {
@@ -772,6 +802,7 @@ async function discoverPluginsWithInit(options) {
772
802
  initExport: initConfig.export,
773
803
  dependencies
774
804
  });
805
+ visitedPackages.add(pluginPath);
775
806
  if (verbose) {
776
807
  console.log(
777
808
  `[PluginInit] Found local plugin with init: ${manifest.name || entry.name}`
@@ -786,11 +817,13 @@ async function discoverPluginsWithInit(options) {
786
817
  if (fs.existsSync(projectPackageJsonPath)) {
787
818
  try {
788
819
  const projectPackageJson = JSON.parse(fs.readFileSync(projectPackageJsonPath, "utf-8"));
789
- const allDeps = {
790
- ...projectPackageJson.dependencies,
791
- ...projectPackageJson.devDependencies
792
- };
793
- for (const depName of Object.keys(allDeps)) {
820
+ const initialDeps = Object.keys(projectPackageJson.dependencies || {});
821
+ const packagesToCheck = [...initialDeps];
822
+ while (packagesToCheck.length > 0) {
823
+ const depName = packagesToCheck.shift();
824
+ if (visitedPackages.has(depName))
825
+ continue;
826
+ visitedPackages.add(depName);
794
827
  let pluginYamlPath;
795
828
  try {
796
829
  pluginYamlPath = require2.resolve(`${depName}/plugin.yaml`, {
@@ -819,6 +852,11 @@ async function discoverPluginsWithInit(options) {
819
852
  if (verbose) {
820
853
  console.log(`[PluginInit] Found NPM plugin with init: ${depName}`);
821
854
  }
855
+ for (const transitiveDep of dependencies) {
856
+ if (!visitedPackages.has(transitiveDep)) {
857
+ packagesToCheck.push(transitiveDep);
858
+ }
859
+ }
822
860
  }
823
861
  } catch (error) {
824
862
  console.warn(`[PluginInit] Failed to scan NPM plugins: ${error}`);
@@ -961,9 +999,157 @@ function preparePluginClientInits(plugins) {
961
999
  };
962
1000
  });
963
1001
  }
1002
+ function makeCacheKey(jayHtmlPath, params) {
1003
+ const sortedParams = Object.keys(params).sort().reduce(
1004
+ (acc, key) => {
1005
+ acc[key] = params[key];
1006
+ return acc;
1007
+ },
1008
+ {}
1009
+ );
1010
+ return `${jayHtmlPath}:${JSON.stringify(sortedParams)}`;
1011
+ }
1012
+ function hashParams(params) {
1013
+ const sortedParams = Object.keys(params).sort().reduce(
1014
+ (acc, key) => {
1015
+ acc[key] = params[key];
1016
+ return acc;
1017
+ },
1018
+ {}
1019
+ );
1020
+ const json = JSON.stringify(sortedParams);
1021
+ if (json === "{}")
1022
+ return "";
1023
+ return "_" + crypto.createHash("md5").update(json).digest("hex").substring(0, 8);
1024
+ }
1025
+ class SlowRenderCache {
1026
+ /**
1027
+ * @param cacheDir - Directory where pre-rendered jay-html files are stored
1028
+ * @param pagesRoot - Root directory of the pages (for relative path calculation)
1029
+ */
1030
+ constructor(cacheDir, pagesRoot) {
1031
+ __publicField(this, "cache", /* @__PURE__ */ new Map());
1032
+ __publicField(this, "pathToKeys", /* @__PURE__ */ new Map());
1033
+ __publicField(this, "cacheDir");
1034
+ __publicField(this, "pagesRoot");
1035
+ this.cacheDir = cacheDir;
1036
+ this.pagesRoot = pagesRoot;
1037
+ }
1038
+ /**
1039
+ * Get a cached pre-rendered jay-html entry
1040
+ */
1041
+ get(jayHtmlPath, params) {
1042
+ const key = makeCacheKey(jayHtmlPath, params);
1043
+ return this.cache.get(key);
1044
+ }
1045
+ /**
1046
+ * Store a pre-rendered jay-html entry in the cache.
1047
+ * Writes the pre-rendered content to disk and stores metadata in memory.
1048
+ */
1049
+ async set(jayHtmlPath, params, preRenderedJayHtml, slowViewState, carryForward) {
1050
+ const key = makeCacheKey(jayHtmlPath, params);
1051
+ const relativePath = path__default.relative(this.pagesRoot, jayHtmlPath);
1052
+ const dir = path__default.dirname(relativePath);
1053
+ const basename = path__default.basename(relativePath, ".jay-html");
1054
+ const paramsHash = hashParams(params);
1055
+ const cacheFileName = `${basename}${paramsHash}.jay-html`;
1056
+ const preRenderedPath = path__default.join(this.cacheDir, dir, cacheFileName);
1057
+ await fs$1.mkdir(path__default.dirname(preRenderedPath), { recursive: true });
1058
+ await fs$1.writeFile(preRenderedPath, preRenderedJayHtml, "utf-8");
1059
+ if (!this.pathToKeys.has(jayHtmlPath)) {
1060
+ this.pathToKeys.set(jayHtmlPath, /* @__PURE__ */ new Set());
1061
+ }
1062
+ this.pathToKeys.get(jayHtmlPath).add(key);
1063
+ const entry = {
1064
+ preRenderedPath,
1065
+ slowViewState,
1066
+ carryForward,
1067
+ createdAt: Date.now(),
1068
+ sourcePath: jayHtmlPath
1069
+ };
1070
+ this.cache.set(key, entry);
1071
+ return preRenderedPath;
1072
+ }
1073
+ /**
1074
+ * Check if a pre-rendered entry exists for the given path and params
1075
+ */
1076
+ has(jayHtmlPath, params) {
1077
+ const key = makeCacheKey(jayHtmlPath, params);
1078
+ return this.cache.has(key);
1079
+ }
1080
+ /**
1081
+ * Invalidate all cached entries for a given jay-html source path.
1082
+ * This is called when the source file changes.
1083
+ * Also deletes the cached files from disk.
1084
+ */
1085
+ async invalidate(jayHtmlPath) {
1086
+ const keys = this.pathToKeys.get(jayHtmlPath);
1087
+ if (keys) {
1088
+ for (const key of keys) {
1089
+ const entry = this.cache.get(key);
1090
+ if (entry) {
1091
+ try {
1092
+ await fs$1.unlink(entry.preRenderedPath);
1093
+ } catch {
1094
+ }
1095
+ }
1096
+ this.cache.delete(key);
1097
+ }
1098
+ this.pathToKeys.delete(jayHtmlPath);
1099
+ }
1100
+ }
1101
+ /**
1102
+ * Invalidate all entries that depend on a changed file.
1103
+ * The changedPath could be:
1104
+ * - A jay-html file itself
1105
+ * - A component file (page.ts)
1106
+ * - Any other dependency
1107
+ *
1108
+ * @param changedPath - Absolute path to the changed file
1109
+ * @param resolveDependencies - Optional function to resolve which jay-html files depend on the changed file
1110
+ */
1111
+ async invalidateByDependency(changedPath, resolveDependencies) {
1112
+ if (changedPath.endsWith(".jay-html")) {
1113
+ await this.invalidate(changedPath);
1114
+ return;
1115
+ }
1116
+ if (resolveDependencies) {
1117
+ const dependentPaths = resolveDependencies(changedPath);
1118
+ for (const depPath of dependentPaths) {
1119
+ await this.invalidate(depPath);
1120
+ }
1121
+ }
1122
+ }
1123
+ /**
1124
+ * Clear all cached entries and delete cached files from disk
1125
+ */
1126
+ async clear() {
1127
+ for (const entry of this.cache.values()) {
1128
+ try {
1129
+ await fs$1.unlink(entry.preRenderedPath);
1130
+ } catch {
1131
+ }
1132
+ }
1133
+ this.cache.clear();
1134
+ this.pathToKeys.clear();
1135
+ }
1136
+ /**
1137
+ * Get the number of cached entries
1138
+ */
1139
+ get size() {
1140
+ return this.cache.size;
1141
+ }
1142
+ /**
1143
+ * Get all cached jay-html paths (for debugging/monitoring)
1144
+ */
1145
+ getCachedPaths() {
1146
+ return Array.from(this.pathToKeys.keys());
1147
+ }
1148
+ }
964
1149
  export {
965
1150
  ActionRegistry,
966
1151
  DevSlowlyChangingPhase,
1152
+ SlowRenderCache,
967
1153
  actionRegistry,
968
1154
  clearActionRegistry,
969
1155
  clearClientInitData,
@@ -992,6 +1178,7 @@ export {
992
1178
  registerService,
993
1179
  renderFastChangingData,
994
1180
  resolveServices,
1181
+ runAction,
995
1182
  runInitCallbacks,
996
1183
  runLoadParams,
997
1184
  runShutdownCallbacks,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/stack-server-runtime",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.mts",
@@ -26,18 +26,18 @@
26
26
  "test:watch": "vitest"
27
27
  },
28
28
  "dependencies": {
29
- "@jay-framework/compiler-jay-html": "^0.10.0",
30
- "@jay-framework/compiler-shared": "^0.10.0",
31
- "@jay-framework/component": "^0.10.0",
32
- "@jay-framework/fullstack-component": "^0.10.0",
33
- "@jay-framework/runtime": "^0.10.0",
34
- "@jay-framework/stack-route-scanner": "^0.10.0",
35
- "@jay-framework/view-state-merge": "^0.10.0"
29
+ "@jay-framework/compiler-jay-html": "^0.11.0",
30
+ "@jay-framework/compiler-shared": "^0.11.0",
31
+ "@jay-framework/component": "^0.11.0",
32
+ "@jay-framework/fullstack-component": "^0.11.0",
33
+ "@jay-framework/runtime": "^0.11.0",
34
+ "@jay-framework/stack-route-scanner": "^0.11.0",
35
+ "@jay-framework/view-state-merge": "^0.11.0"
36
36
  },
37
37
  "devDependencies": {
38
- "@jay-framework/dev-environment": "^0.10.0",
39
- "@jay-framework/jay-cli": "^0.10.0",
40
- "@jay-framework/stack-client-runtime": "^0.10.0",
38
+ "@jay-framework/dev-environment": "^0.11.0",
39
+ "@jay-framework/jay-cli": "^0.11.0",
40
+ "@jay-framework/stack-client-runtime": "^0.11.0",
41
41
  "@types/express": "^5.0.2",
42
42
  "@types/node": "^22.15.21",
43
43
  "nodemon": "^3.0.3",