@jay-framework/stack-server-runtime 0.13.0 → 0.15.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 +157 -106
  2. package/dist/index.js +752 -366
  3. package/package.json +13 -12
package/dist/index.js CHANGED
@@ -4,20 +4,20 @@ var __publicField = (obj, key, value) => {
4
4
  __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
5
  return value;
6
6
  };
7
- import { notFound, partialRender, phaseOutput, isJayAction } from "@jay-framework/fullstack-component";
8
- import fs$1 from "node:fs/promises";
9
- import * as path from "node:path";
10
- import path__default from "node:path";
11
- import { parseJayFile, JAY_IMPORT_RESOLVER, parseAction } from "@jay-framework/compiler-jay-html";
12
- import { createRequire } from "module";
13
- import * as fs from "node:fs";
14
- import { createRequire as createRequire$1 } from "node:module";
7
+ import { phaseOutput, isJayAction } from "@jay-framework/fullstack-component";
15
8
  import "prettier";
16
9
  import "js-beautify";
17
- import fs$2 from "fs";
10
+ import fs$1 from "fs";
18
11
  import path$1 from "path";
19
12
  import YAML from "yaml";
13
+ import { createRequire } from "module";
14
+ import { parseJayFile, JAY_IMPORT_RESOLVER, generateServerElementFile, discoverHeadlessInstances, parseAction } from "@jay-framework/compiler-jay-html";
15
+ import fs$2 from "node:fs/promises";
16
+ import * as path from "node:path";
17
+ import path__default from "node:path";
20
18
  import { getLogger } from "@jay-framework/logger";
19
+ import * as fs from "node:fs";
20
+ import { createRequire as createRequire$1 } from "node:module";
21
21
  import crypto from "node:crypto";
22
22
  const serviceRegistry = /* @__PURE__ */ new Map();
23
23
  function registerService(marker, service) {
@@ -83,37 +83,8 @@ function getClientInitDataForKey(key) {
83
83
  function clearClientInitData() {
84
84
  clientInitData = {};
85
85
  }
86
- function isLeftSideParamsSubsetOfRightSideParams(left, right) {
87
- return Object.keys(left).reduce((prev, curr) => prev && left[curr] === right[curr], true);
88
- }
89
- async function findMatchingParams(search, searchTarget) {
90
- for await (const paramsArray of searchTarget) {
91
- if (paramsArray.find((params) => isLeftSideParamsSubsetOfRightSideParams(search, params)))
92
- return true;
93
- }
94
- return false;
95
- }
96
86
  class DevSlowlyChangingPhase {
97
- constructor(dontCacheSlowly) {
98
- this.dontCacheSlowly = dontCacheSlowly;
99
- }
100
- async runSlowlyForPage(pageParams, pageProps, parts) {
101
- for (const part of parts) {
102
- const { compDefinition, contractInfo } = part;
103
- if (compDefinition.loadParams) {
104
- const services = resolveServices(compDefinition.services);
105
- const loadParamsArgs = contractInfo ? [
106
- ...services,
107
- {
108
- contractName: contractInfo.contractName,
109
- metadata: contractInfo.metadata
110
- }
111
- ] : services;
112
- const compParams = compDefinition.loadParams(loadParamsArgs);
113
- if (!await findMatchingParams(pageParams, compParams))
114
- return notFound();
115
- }
116
- }
87
+ async runSlowlyForPage(pageParams, pageProps, parts, discoveredInstances, headlessInstanceComponents, jayHtmlPath) {
117
88
  let slowlyViewState = {};
118
89
  let carryForward = {};
119
90
  for (const part of parts) {
@@ -144,7 +115,57 @@ class DevSlowlyChangingPhase {
144
115
  return slowlyRenderedPart;
145
116
  }
146
117
  }
147
- return partialRender(slowlyViewState, carryForward);
118
+ if (discoveredInstances && discoveredInstances.length > 0 && headlessInstanceComponents) {
119
+ const componentByContractName = /* @__PURE__ */ new Map();
120
+ for (const comp of headlessInstanceComponents) {
121
+ componentByContractName.set(comp.contractName, comp);
122
+ }
123
+ const instancePhaseData = {
124
+ discovered: [],
125
+ carryForwards: {}
126
+ };
127
+ const instanceSlowViewStates = {};
128
+ const instanceResolvedData = [];
129
+ for (const instance of discoveredInstances) {
130
+ const comp = componentByContractName.get(instance.contractName);
131
+ if (!comp)
132
+ continue;
133
+ const coordKey = instance.coordinate.join("/");
134
+ const contractProps = comp.contract?.props ?? [];
135
+ const normalizedProps = {};
136
+ for (const [key, value] of Object.entries(instance.props)) {
137
+ const match = contractProps.find(
138
+ (p) => p.name.toLowerCase() === key.toLowerCase()
139
+ );
140
+ normalizedProps[match ? match.name : key] = value;
141
+ }
142
+ instancePhaseData.discovered.push({
143
+ contractName: instance.contractName,
144
+ props: normalizedProps,
145
+ coordinate: instance.coordinate
146
+ });
147
+ if (comp.compDefinition.slowlyRender) {
148
+ const services = resolveServices(comp.compDefinition.services);
149
+ const slowResult = await comp.compDefinition.slowlyRender(
150
+ normalizedProps,
151
+ ...services
152
+ );
153
+ if (slowResult.kind === "PhaseOutput") {
154
+ instanceSlowViewStates[coordKey] = slowResult.rendered;
155
+ instancePhaseData.carryForwards[coordKey] = slowResult.carryForward;
156
+ instanceResolvedData.push({
157
+ coordinate: instance.coordinate,
158
+ contract: comp.contract,
159
+ slowViewState: slowResult.rendered
160
+ });
161
+ }
162
+ }
163
+ }
164
+ carryForward.__instances = instancePhaseData;
165
+ carryForward.__instanceSlowViewStates = instanceSlowViewStates;
166
+ carryForward.__instanceResolvedData = instanceResolvedData;
167
+ }
168
+ return phaseOutput(slowlyViewState, carryForward);
148
169
  }
149
170
  }
150
171
  async function runLoadParams(compDefinition, services) {
@@ -152,7 +173,199 @@ async function runLoadParams(compDefinition, services) {
152
173
  }
153
174
  function runSlowlyChangingRender(compDefinition) {
154
175
  }
155
- async function renderFastChangingData(pageParams, pageProps, carryForward, parts) {
176
+ var __defProp2 = Object.defineProperty;
177
+ var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
178
+ var __publicField2 = (obj, key, value) => {
179
+ __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
180
+ return value;
181
+ };
182
+ class JayAtomicType {
183
+ constructor(name) {
184
+ __publicField2(this, "kind", 0);
185
+ this.name = name;
186
+ }
187
+ }
188
+ new JayAtomicType("string");
189
+ new JayAtomicType("number");
190
+ new JayAtomicType("boolean");
191
+ new JayAtomicType("Date");
192
+ new JayAtomicType("Unknown");
193
+ class JayObjectType {
194
+ constructor(name, props) {
195
+ __publicField2(this, "kind", 8);
196
+ this.name = name;
197
+ this.props = props;
198
+ }
199
+ }
200
+ new JayObjectType("Error", {
201
+ message: new JayAtomicType("string"),
202
+ name: new JayAtomicType("string"),
203
+ stack: new JayAtomicType("string")
204
+ });
205
+ function isOptionalType(aType) {
206
+ return aType.kind === 13;
207
+ }
208
+ function isAtomicType(aType) {
209
+ return aType.kind === 0;
210
+ }
211
+ function isEnumType(aType) {
212
+ return aType.kind === 2;
213
+ }
214
+ function isImportedType(aType) {
215
+ return aType.kind === 4;
216
+ }
217
+ function isObjectType(aType) {
218
+ return aType.kind === 8;
219
+ }
220
+ function isArrayType(aType) {
221
+ return aType.kind === 9;
222
+ }
223
+ function jayTypeToJsonSchema(type) {
224
+ if (isOptionalType(type)) {
225
+ return jayTypeToJsonSchema(type.innerType);
226
+ }
227
+ if (isAtomicType(type)) {
228
+ const name = type.name.toLowerCase();
229
+ if (name === "string" || name === "number" || name === "boolean") {
230
+ return { type: name };
231
+ }
232
+ return { type: "string" };
233
+ }
234
+ if (isEnumType(type)) {
235
+ return { type: "string", enum: type.values };
236
+ }
237
+ if (isImportedType(type)) {
238
+ return { type: "object", description: `Contract: ${type.name}` };
239
+ }
240
+ if (isArrayType(type)) {
241
+ const itemSchema = jayTypeToJsonSchema(type.itemType);
242
+ if (itemSchema) {
243
+ return { type: "array", items: itemSchema };
244
+ }
245
+ return { type: "array" };
246
+ }
247
+ if (isObjectType(type)) {
248
+ const properties = {};
249
+ const required = [];
250
+ for (const [key, propType] of Object.entries(type.props)) {
251
+ const isOpt = isOptionalType(propType);
252
+ const schema = jayTypeToJsonSchema(propType);
253
+ if (schema) {
254
+ properties[key] = schema;
255
+ if (!isOpt) {
256
+ required.push(key);
257
+ }
258
+ }
259
+ }
260
+ return {
261
+ type: "object",
262
+ properties,
263
+ ...required.length > 0 && { required }
264
+ };
265
+ }
266
+ return null;
267
+ }
268
+ var RuntimeMode = /* @__PURE__ */ ((RuntimeMode2) => {
269
+ RuntimeMode2["MainTrusted"] = "mainTrusted";
270
+ RuntimeMode2["MainSandbox"] = "mainSandbox";
271
+ RuntimeMode2["WorkerTrusted"] = "workerTrusted";
272
+ RuntimeMode2["WorkerSandbox"] = "workerSandbox";
273
+ return RuntimeMode2;
274
+ })(RuntimeMode || {});
275
+ const TS_EXTENSION = ".ts";
276
+ const JAY_QUERY_PREFIX = "?jay-";
277
+ const JAY_QUERY_HYDRATE = `${JAY_QUERY_PREFIX}hydrate`;
278
+ [
279
+ // Hydrate target
280
+ {
281
+ pattern: JAY_QUERY_HYDRATE,
282
+ buildEnv: "client",
283
+ isHydrate: true
284
+ },
285
+ // Build environments
286
+ {
287
+ pattern: `${JAY_QUERY_PREFIX}${"client"}`,
288
+ buildEnv: "client"
289
+ /* Client */
290
+ },
291
+ {
292
+ pattern: `${JAY_QUERY_PREFIX}${"server"}`,
293
+ buildEnv: "server"
294
+ /* Server */
295
+ },
296
+ // Runtime modes (with .ts suffix)
297
+ {
298
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.MainSandbox}${TS_EXTENSION}`,
299
+ runtimeMode: RuntimeMode.MainSandbox
300
+ },
301
+ {
302
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerTrusted}${TS_EXTENSION}`,
303
+ runtimeMode: RuntimeMode.WorkerTrusted
304
+ },
305
+ {
306
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerSandbox}${TS_EXTENSION}`,
307
+ runtimeMode: RuntimeMode.WorkerSandbox
308
+ },
309
+ // Runtime modes (without .ts suffix)
310
+ {
311
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.MainSandbox}`,
312
+ runtimeMode: RuntimeMode.MainSandbox
313
+ },
314
+ {
315
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerTrusted}`,
316
+ runtimeMode: RuntimeMode.WorkerTrusted
317
+ },
318
+ {
319
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerSandbox}`,
320
+ runtimeMode: RuntimeMode.WorkerSandbox
321
+ }
322
+ ];
323
+ function checkValidationErrors(withValidations) {
324
+ const { validations } = withValidations;
325
+ if (validations.length > 0) {
326
+ throw new Error(validations.join("\n"));
327
+ }
328
+ return withValidations.val;
329
+ }
330
+ createRequire(import.meta.url);
331
+ function normalizeActionEntry(entry) {
332
+ if (typeof entry === "string") {
333
+ return { name: entry };
334
+ }
335
+ return { name: entry.name, action: entry.action };
336
+ }
337
+ function loadPluginManifest(pluginDir) {
338
+ const pluginYamlPath = path$1.join(pluginDir, "plugin.yaml");
339
+ if (!fs$1.existsSync(pluginYamlPath)) {
340
+ return null;
341
+ }
342
+ try {
343
+ const yamlContent = fs$1.readFileSync(pluginYamlPath, "utf-8");
344
+ return YAML.parse(yamlContent);
345
+ } catch (error) {
346
+ return null;
347
+ }
348
+ }
349
+ function computeForEachInstanceKey(trackByValue, coordinateSuffix) {
350
+ return [trackByValue, coordinateSuffix].toString();
351
+ }
352
+ const s = createRequire(import.meta.url), e = s("typescript");
353
+ new Proxy(e, {
354
+ get(t, r) {
355
+ return t[r];
356
+ }
357
+ });
358
+ function resolvePathValue(obj, path2) {
359
+ return path2.split(".").reduce((current, segment) => current?.[segment], obj);
360
+ }
361
+ function resolveBinding(binding, item) {
362
+ const match = binding.match(/^\{(.+)\}$/);
363
+ if (match) {
364
+ return String(item[match[1]] ?? "");
365
+ }
366
+ return binding;
367
+ }
368
+ async function renderFastChangingData(pageParams, pageProps, carryForward, parts, instancePhaseData, forEachInstances, headlessInstanceComponents, mergedSlowViewState) {
156
369
  let fastViewState = {};
157
370
  let fastCarryForward = {};
158
371
  for (const part of parts) {
@@ -185,12 +398,92 @@ async function renderFastChangingData(pageParams, pageProps, carryForward, parts
185
398
  return fastRenderedPart;
186
399
  }
187
400
  }
401
+ const instanceViewStates = {};
402
+ const instanceCarryForwards = {};
403
+ if (instancePhaseData && headlessInstanceComponents) {
404
+ const componentByContractName = /* @__PURE__ */ new Map();
405
+ for (const comp of headlessInstanceComponents) {
406
+ componentByContractName.set(comp.contractName, comp);
407
+ }
408
+ for (const instance of instancePhaseData.discovered) {
409
+ const coordKey = instance.coordinate.join("/");
410
+ const comp = componentByContractName.get(instance.contractName);
411
+ if (!comp || !comp.compDefinition.fastRender)
412
+ continue;
413
+ const services = resolveServices(comp.compDefinition.services);
414
+ const cf = instancePhaseData.carryForwards[coordKey];
415
+ const fastResult = comp.compDefinition.slowlyRender ? await comp.compDefinition.fastRender(instance.props, cf, ...services) : await comp.compDefinition.fastRender(instance.props, ...services);
416
+ if (fastResult.kind === "PhaseOutput") {
417
+ const instanceSlowVS = carryForward?.__instanceSlowViewStates?.[coordKey];
418
+ instanceViewStates[coordKey] = instanceSlowVS ? { ...instanceSlowVS, ...fastResult.rendered } : fastResult.rendered;
419
+ if (fastResult.carryForward) {
420
+ instanceCarryForwards[coordKey] = fastResult.carryForward;
421
+ }
422
+ }
423
+ }
424
+ }
425
+ if (forEachInstances && forEachInstances.length > 0 && headlessInstanceComponents) {
426
+ const componentByContractName = /* @__PURE__ */ new Map();
427
+ for (const comp of headlessInstanceComponents) {
428
+ componentByContractName.set(comp.contractName, comp);
429
+ }
430
+ const mergedForEachVS = { ...mergedSlowViewState || {}, ...fastViewState };
431
+ for (const instance of forEachInstances) {
432
+ const comp = componentByContractName.get(instance.contractName);
433
+ if (!comp)
434
+ continue;
435
+ const items = resolvePathValue(mergedForEachVS, instance.forEachPath);
436
+ if (!Array.isArray(items))
437
+ continue;
438
+ const contractProps = comp.contract?.props ?? [];
439
+ const normalizePropName = (key) => contractProps.find((p) => p.name.toLowerCase() === key.toLowerCase())?.name ?? key;
440
+ for (const item of items) {
441
+ const trackByValue = String(item[instance.trackBy]);
442
+ const props = {};
443
+ for (const [propName, binding] of Object.entries(instance.propBindings)) {
444
+ props[normalizePropName(propName)] = resolveBinding(String(binding), item);
445
+ }
446
+ if (comp.compDefinition.fastRender) {
447
+ const services = resolveServices(comp.compDefinition.services);
448
+ let slowVS = {};
449
+ let cf = {};
450
+ if (comp.compDefinition.slowlyRender) {
451
+ const slowResult = await comp.compDefinition.slowlyRender(
452
+ props,
453
+ ...services
454
+ );
455
+ if (slowResult.kind === "PhaseOutput") {
456
+ slowVS = slowResult.rendered;
457
+ cf = slowResult.carryForward;
458
+ }
459
+ }
460
+ const fastResult = comp.compDefinition.slowlyRender ? await comp.compDefinition.fastRender(props, cf, ...services) : await comp.compDefinition.fastRender(props, ...services);
461
+ if (fastResult.kind === "PhaseOutput") {
462
+ const coord = computeForEachInstanceKey(
463
+ trackByValue,
464
+ instance.coordinateSuffix
465
+ );
466
+ instanceViewStates[coord] = { ...slowVS, ...fastResult.rendered };
467
+ if (fastResult.carryForward) {
468
+ instanceCarryForwards[coord] = fastResult.carryForward;
469
+ }
470
+ }
471
+ }
472
+ }
473
+ }
474
+ }
475
+ if (Object.keys(instanceViewStates).length > 0) {
476
+ fastViewState.__headlessInstances = instanceViewStates;
477
+ }
478
+ if (Object.keys(instanceCarryForwards).length > 0) {
479
+ fastCarryForward.__headlessInstances = instanceCarryForwards;
480
+ }
188
481
  return Promise.resolve(phaseOutput(fastViewState, fastCarryForward));
189
482
  }
190
- function generateClientScript(defaultViewState, fastCarryForward, parts, jayHtmlPath, trackByMap = {}, clientInitData2 = {}, projectInit, pluginInits = [], options = {}) {
483
+ function buildScriptFragments(parts, clientInitData2, projectInit, pluginInits, options) {
191
484
  const { enableAutomation = true, slowViewState } = options;
192
485
  const hasSlowViewState = slowViewState && Object.keys(slowViewState).length > 0;
193
- const imports = parts.length > 0 ? parts.map((part) => part.clientImport).join("\n") + "\n" : "";
486
+ const partImports = parts.length > 0 ? parts.map((part) => part.clientImport).join("\n") + "\n" : "";
194
487
  const compositeParts = parts.length > 0 ? `[
195
488
  ${parts.map((part) => " " + part.clientPart).join(",\n")}
196
489
  ]` : "[]";
@@ -214,7 +507,7 @@ ${parts.map((part) => " " + part.clientPart).join(",\n")}
214
507
  const clientInitExecution = hasClientInit ? `
215
508
  // Plugin client initialization (in dependency order)
216
509
  ${pluginClientInitCalls}
217
-
510
+
218
511
  // Project client initialization
219
512
  ${projectInitCall}
220
513
  ` : "";
@@ -223,7 +516,28 @@ ${parts.map((part) => " " + part.clientPart).join(",\n")}
223
516
  import { deepMergeViewStates } from "@jay-framework/view-state-merge";` : `import { wrapWithAutomation, AUTOMATION_CONTEXT } from "@jay-framework/runtime-automation";
224
517
  import { registerGlobalContext } from "@jay-framework/runtime";` : "";
225
518
  const slowViewStateDecl = enableAutomation && hasSlowViewState ? `const slowViewState = ${JSON.stringify(slowViewState)};` : "";
226
- const automationWrap = enableAutomation ? hasSlowViewState ? `
519
+ return {
520
+ partImports,
521
+ compositeParts,
522
+ pluginClientInitImports,
523
+ projectInitImport,
524
+ clientInitExecution,
525
+ automationImport,
526
+ slowViewStateDecl
527
+ };
528
+ }
529
+ function buildAutomationWrap(options, mode) {
530
+ const { enableAutomation = true, slowViewState } = options;
531
+ const hasSlowViewState = slowViewState && Object.keys(slowViewState).length > 0;
532
+ const appendDom = mode === "client";
533
+ if (!enableAutomation) {
534
+ return appendDom ? `
535
+ target.appendChild(instance.element.dom);` : "";
536
+ }
537
+ const appendLine = appendDom ? `
538
+ target.appendChild(wrapped.element.dom);` : "";
539
+ if (hasSlowViewState) {
540
+ return `
227
541
  // Wrap with automation for dev tooling
228
542
  // Deep merge slow+fast ViewState so automation can see full page state
229
543
  const fullViewState = deepMergeViewStates(slowViewState, {...viewState, ...fastCarryForward}, trackByMap);
@@ -231,14 +545,27 @@ ${parts.map((part) => " " + part.clientPart).join(",\n")}
231
545
  registerGlobalContext(AUTOMATION_CONTEXT, wrapped.automation);
232
546
  window.__jay = window.__jay || {};
233
547
  window.__jay.automation = wrapped.automation;
234
- target.appendChild(wrapped.element.dom);` : `
548
+ window.dispatchEvent(new Event('jay:automation-ready'));${appendLine}`;
549
+ }
550
+ return `
235
551
  // Wrap with automation for dev tooling
236
552
  const wrapped = wrapWithAutomation(instance);
237
553
  registerGlobalContext(AUTOMATION_CONTEXT, wrapped.automation);
238
554
  window.__jay = window.__jay || {};
239
555
  window.__jay.automation = wrapped.automation;
240
- target.appendChild(wrapped.element.dom);` : `
241
- target.appendChild(instance.element.dom);`;
556
+ window.dispatchEvent(new Event('jay:automation-ready'));${appendLine}`;
557
+ }
558
+ function generateClientScript(defaultViewState, fastCarryForward, parts, jayHtmlPath, trackByMap = {}, clientInitData2 = {}, projectInit, pluginInits = [], options = {}) {
559
+ const {
560
+ partImports,
561
+ compositeParts,
562
+ pluginClientInitImports,
563
+ projectInitImport,
564
+ clientInitExecution,
565
+ automationImport,
566
+ slowViewStateDecl
567
+ } = buildScriptFragments(parts, clientInitData2, projectInit, pluginInits, options);
568
+ const automationWrap = buildAutomationWrap(options, "client");
242
569
  return `<!doctype html>
243
570
  <html lang="en">
244
571
  <head>
@@ -254,7 +581,7 @@ ${parts.map((part) => " " + part.clientPart).join(",\n")}
254
581
  ${pluginClientInitImports}
255
582
  ${projectInitImport}
256
583
  import { render } from '${jayHtmlPath}';
257
- ${imports}${slowViewStateDecl}
584
+ ${partImports}${slowViewStateDecl}
258
585
  const viewState = ${JSON.stringify(defaultViewState)};
259
586
  const fastCarryForward = ${JSON.stringify(fastCarryForward)};
260
587
  const trackByMap = ${JSON.stringify(trackByMap)};
@@ -268,9 +595,171 @@ ${automationWrap}
268
595
  </body>
269
596
  </html>`;
270
597
  }
598
+ const serverModuleCache = /* @__PURE__ */ new Map();
599
+ function invalidateServerElementCache(jayHtmlPath) {
600
+ if (serverModuleCache.delete(jayHtmlPath)) {
601
+ getLogger().info(`[SSR] Invalidated server element cache for ${jayHtmlPath}`);
602
+ }
603
+ }
604
+ function clearServerElementCache() {
605
+ serverModuleCache.clear();
606
+ }
607
+ async function generateSSRPageHtml(vite, jayHtmlContent, jayHtmlFilename, jayHtmlDir, viewState, jayHtmlImportPath, parts, carryForward, trackByMap = {}, clientInitData2 = {}, buildFolder, projectRoot, routeDir, tsConfigFilePath, projectInit, pluginInits = [], options = {}) {
608
+ const jayHtmlPath = path__default.join(jayHtmlDir, jayHtmlFilename);
609
+ let cached = serverModuleCache.get(jayHtmlPath);
610
+ if (!cached) {
611
+ cached = await compileAndLoadServerElement(
612
+ vite,
613
+ jayHtmlContent,
614
+ jayHtmlFilename,
615
+ jayHtmlDir,
616
+ buildFolder,
617
+ projectRoot,
618
+ routeDir,
619
+ tsConfigFilePath
620
+ );
621
+ serverModuleCache.set(jayHtmlPath, cached);
622
+ }
623
+ const htmlChunks = [];
624
+ const asyncPromises = [];
625
+ const ctx = {
626
+ write: (chunk) => {
627
+ htmlChunks.push(chunk);
628
+ },
629
+ onAsync: (promise, id, templates) => {
630
+ const asyncPromise = promise.then(
631
+ (val) => {
632
+ if (templates.resolved) {
633
+ return templates.resolved(val);
634
+ }
635
+ return "";
636
+ },
637
+ (err) => {
638
+ if (templates.rejected) {
639
+ return templates.rejected(err);
640
+ }
641
+ return "";
642
+ }
643
+ );
644
+ asyncPromises.push(asyncPromise);
645
+ }
646
+ };
647
+ cached.renderToStream(viewState, ctx);
648
+ const asyncResults = await Promise.all(asyncPromises);
649
+ const ssrHtml = htmlChunks.join("");
650
+ const asyncScripts = asyncResults.filter((r) => r !== "").join("");
651
+ const hydrationScript = generateHydrationScript(
652
+ viewState,
653
+ carryForward,
654
+ parts,
655
+ jayHtmlImportPath,
656
+ trackByMap,
657
+ clientInitData2,
658
+ projectInit,
659
+ pluginInits,
660
+ options
661
+ );
662
+ const headLinksHtml = cached.headLinks.map((link) => {
663
+ const attrs = Object.entries(link.attributes).map(([k, v]) => ` ${k}="${v}"`).join("");
664
+ return ` <link rel="${link.rel}" href="${link.href}"${attrs} />`;
665
+ }).join("\n");
666
+ const inlineCss = cached.css ? ` <style>
667
+ ${cached.css}
668
+ </style>` : "";
669
+ const headExtras = [headLinksHtml, inlineCss].filter((_) => _).join("\n");
670
+ return `<!doctype html>
671
+ <html lang="en">
672
+ <head>
673
+ <meta charset="UTF-8" />
674
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
675
+ <title>Vite + TS</title>
676
+ ${headExtras ? headExtras + "\n" : ""} </head>
677
+ <body>
678
+ <div id="target">${ssrHtml}</div>${asyncScripts}
679
+ ${hydrationScript}
680
+ </body>
681
+ </html>`;
682
+ }
683
+ function rebaseRelativeImports(code, fromDir, toDir) {
684
+ return code.replace(/from "(\.\.\/[^"]+)"/g, (_match, relPath) => {
685
+ const absolutePath = path__default.resolve(fromDir, relPath);
686
+ let newRelPath = path__default.relative(toDir, absolutePath);
687
+ if (!newRelPath.startsWith(".")) {
688
+ newRelPath = "./" + newRelPath;
689
+ }
690
+ return `from "${newRelPath}"`;
691
+ });
692
+ }
693
+ async function compileAndLoadServerElement(vite, jayHtmlContent, jayHtmlFilename, jayHtmlDir, buildFolder, projectRoot, routeDir, tsConfigFilePath) {
694
+ const jayFile = await parseJayFile(
695
+ jayHtmlContent,
696
+ jayHtmlFilename,
697
+ jayHtmlDir,
698
+ { relativePath: tsConfigFilePath },
699
+ JAY_IMPORT_RESOLVER,
700
+ projectRoot
701
+ );
702
+ const parsedJayFile = checkValidationErrors(jayFile);
703
+ const pageName = jayHtmlFilename.replace(".jay-html", "");
704
+ const debugCoordinatePreprocessPath = path__default.join(
705
+ buildFolder,
706
+ "debug",
707
+ routeDir,
708
+ `${pageName}.coordinate-preprocess.jay-html`
709
+ );
710
+ const serverElementOptions = {
711
+ debugCoordinatePreprocessPath
712
+ };
713
+ const serverElementCode = checkValidationErrors(
714
+ generateServerElementFile(parsedJayFile, serverElementOptions)
715
+ );
716
+ const serverElementDir = path__default.join(buildFolder, "pre-rendered", routeDir);
717
+ await fs$2.mkdir(serverElementDir, { recursive: true });
718
+ const adjustedCode = rebaseRelativeImports(serverElementCode, jayHtmlDir, serverElementDir);
719
+ const serverElementFilename = jayHtmlFilename.replace(".jay-html", ".server-element.ts");
720
+ const serverElementPath = path__default.join(serverElementDir, serverElementFilename);
721
+ await fs$2.writeFile(serverElementPath, adjustedCode, "utf-8");
722
+ const serverModule = await vite.ssrLoadModule(serverElementPath);
723
+ return {
724
+ renderToStream: serverModule.renderToStream,
725
+ headLinks: parsedJayFile.headLinks,
726
+ css: parsedJayFile.css
727
+ };
728
+ }
729
+ function generateHydrationScript(defaultViewState, fastCarryForward, parts, jayHtmlPath, trackByMap = {}, clientInitData2 = {}, projectInit, pluginInits = [], options = {}) {
730
+ const {
731
+ partImports,
732
+ compositeParts,
733
+ pluginClientInitImports,
734
+ projectInitImport,
735
+ clientInitExecution,
736
+ automationImport,
737
+ slowViewStateDecl
738
+ } = buildScriptFragments(parts, clientInitData2, projectInit, pluginInits, options);
739
+ const automationWrap = buildAutomationWrap(options, "hydrate");
740
+ const hydrateImportPath = `${jayHtmlPath}${JAY_QUERY_HYDRATE}`;
741
+ return `<script type="module">
742
+ import {hydrateCompositeJayComponent} from "@jay-framework/stack-client-runtime";
743
+ ${automationImport}
744
+ ${pluginClientInitImports}
745
+ ${projectInitImport}
746
+ import { hydrate } from '${hydrateImportPath}';
747
+ ${partImports}${slowViewStateDecl}
748
+ const viewState = ${JSON.stringify(defaultViewState)};
749
+ const fastCarryForward = ${JSON.stringify(fastCarryForward)};
750
+ const trackByMap = ${JSON.stringify(trackByMap)};
751
+
752
+ const target = document.getElementById('target');
753
+ const rootElement = target.firstElementChild;
754
+ const pageComp = hydrateCompositeJayComponent(hydrate, viewState, fastCarryForward, ${compositeParts}, trackByMap, rootElement);
755
+ ${clientInitExecution}
756
+ const instance = pageComp({/* placeholder for page props */});
757
+ ${automationWrap}
758
+ <\/script>`;
759
+ }
271
760
  const require$3 = createRequire(import.meta.url);
272
761
  async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfig, options) {
273
- const exists = await fs$1.access(route.compPath, fs$1.constants.F_OK).then(() => true).catch(() => false);
762
+ const exists = await fs$2.access(route.compPath, fs$2.constants.F_OK).then(() => true).catch(() => false);
274
763
  const parts = [];
275
764
  if (exists) {
276
765
  const pageComponent = (await vite.ssrLoadModule(route.compPath)).page;
@@ -281,7 +770,7 @@ async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfi
281
770
  });
282
771
  }
283
772
  const jayHtmlFilePath = options?.preRenderedPath ?? route.jayHtmlPath;
284
- const jayHtmlSource = (await fs$1.readFile(jayHtmlFilePath)).toString();
773
+ const jayHtmlSource = options?.preRenderedContent ?? (await fs$2.readFile(jayHtmlFilePath)).toString();
285
774
  const fileName = path__default.basename(route.jayHtmlPath);
286
775
  const dirName = path__default.dirname(route.jayHtmlPath);
287
776
  const jayHtmlWithValidations = await parseJayFile(
@@ -338,13 +827,16 @@ async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfi
338
827
  contract: hi.contract,
339
828
  contractPath: hi.contractPath
340
829
  }));
830
+ const discoveryResult = headlessInstanceComponents.length > 0 ? discoverHeadlessInstances(jayHtmlSource) : { instances: [], forEachInstances: [], preRenderedJayHtml: jayHtmlSource };
341
831
  return {
342
832
  parts,
343
833
  serverTrackByMap: jayHtml.serverTrackByMap,
344
834
  clientTrackByMap: jayHtml.clientTrackByMap,
345
835
  usedPackages,
346
836
  headlessContracts,
347
- headlessInstanceComponents
837
+ headlessInstanceComponents,
838
+ discoveredInstances: discoveryResult.instances,
839
+ forEachInstances: discoveryResult.forEachInstances
348
840
  };
349
841
  });
350
842
  }
@@ -362,8 +854,14 @@ async function slowRenderInstances(discovered, headlessInstanceComponents) {
362
854
  if (!comp || !comp.compDefinition.slowlyRender) {
363
855
  continue;
364
856
  }
857
+ const contractProps = comp.contract?.props ?? [];
858
+ const normalizedProps = {};
859
+ for (const [key, value] of Object.entries(instance.props)) {
860
+ const match = contractProps.find((p) => p.name.toLowerCase() === key.toLowerCase());
861
+ normalizedProps[match ? match.name : key] = value;
862
+ }
365
863
  const services = resolveServices(comp.compDefinition.services);
366
- const slowResult = await comp.compDefinition.slowlyRender(instance.props, ...services);
864
+ const slowResult = await comp.compDefinition.slowlyRender(normalizedProps, ...services);
367
865
  if (slowResult.kind === "PhaseOutput") {
368
866
  const coordKey = instance.coordinate.join("/");
369
867
  resolvedData.push({
@@ -375,7 +873,7 @@ async function slowRenderInstances(discovered, headlessInstanceComponents) {
375
873
  carryForwards[coordKey] = slowResult.carryForward;
376
874
  discoveredForFast.push({
377
875
  contractName: instance.contractName,
378
- props: instance.props,
876
+ props: normalizedProps,
379
877
  coordinate: instance.coordinate
380
878
  });
381
879
  }
@@ -551,200 +1049,35 @@ class ActionRegistry {
551
1049
  const parts = [];
552
1050
  if (maxAge !== void 0) {
553
1051
  parts.push(`max-age=${maxAge}`);
554
- }
555
- if (staleWhileRevalidate !== void 0) {
556
- parts.push(`stale-while-revalidate=${staleWhileRevalidate}`);
557
- }
558
- return parts.length > 0 ? parts.join(", ") : void 0;
559
- }
560
- }
561
- const actionRegistry = new ActionRegistry();
562
- function registerAction(action) {
563
- actionRegistry.register(action);
564
- }
565
- function getRegisteredAction(actionName) {
566
- return actionRegistry.get(actionName);
567
- }
568
- function hasAction(actionName) {
569
- return actionRegistry.has(actionName);
570
- }
571
- function getRegisteredActionNames() {
572
- return actionRegistry.getNames();
573
- }
574
- function clearActionRegistry() {
575
- actionRegistry.clear();
576
- }
577
- async function executeAction(actionName, input) {
578
- return actionRegistry.execute(actionName, input);
579
- }
580
- function getActionCacheHeaders(actionName) {
581
- return actionRegistry.getCacheHeaders(actionName);
582
- }
583
- var __defProp2 = Object.defineProperty;
584
- var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
585
- var __publicField2 = (obj, key, value) => {
586
- __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
587
- return value;
588
- };
589
- class JayAtomicType {
590
- constructor(name) {
591
- __publicField2(this, "kind", 0);
592
- this.name = name;
593
- }
594
- }
595
- new JayAtomicType("string");
596
- new JayAtomicType("number");
597
- new JayAtomicType("boolean");
598
- new JayAtomicType("Date");
599
- new JayAtomicType("Unknown");
600
- class JayObjectType {
601
- constructor(name, props) {
602
- __publicField2(this, "kind", 8);
603
- this.name = name;
604
- this.props = props;
1052
+ }
1053
+ if (staleWhileRevalidate !== void 0) {
1054
+ parts.push(`stale-while-revalidate=${staleWhileRevalidate}`);
1055
+ }
1056
+ return parts.length > 0 ? parts.join(", ") : void 0;
605
1057
  }
606
1058
  }
607
- new JayObjectType("Error", {
608
- message: new JayAtomicType("string"),
609
- name: new JayAtomicType("string"),
610
- stack: new JayAtomicType("string")
611
- });
612
- function isOptionalType(aType) {
613
- return aType.kind === 13;
614
- }
615
- function isAtomicType(aType) {
616
- return aType.kind === 0;
617
- }
618
- function isEnumType(aType) {
619
- return aType.kind === 2;
1059
+ const actionRegistry = new ActionRegistry();
1060
+ function registerAction(action) {
1061
+ actionRegistry.register(action);
620
1062
  }
621
- function isImportedType(aType) {
622
- return aType.kind === 4;
1063
+ function getRegisteredAction(actionName) {
1064
+ return actionRegistry.get(actionName);
623
1065
  }
624
- function isObjectType(aType) {
625
- return aType.kind === 8;
1066
+ function hasAction(actionName) {
1067
+ return actionRegistry.has(actionName);
626
1068
  }
627
- function isArrayType(aType) {
628
- return aType.kind === 9;
1069
+ function getRegisteredActionNames() {
1070
+ return actionRegistry.getNames();
629
1071
  }
630
- function jayTypeToJsonSchema(type) {
631
- if (isOptionalType(type)) {
632
- return jayTypeToJsonSchema(type.innerType);
633
- }
634
- if (isAtomicType(type)) {
635
- const name = type.name.toLowerCase();
636
- if (name === "string" || name === "number" || name === "boolean") {
637
- return { type: name };
638
- }
639
- return { type: "string" };
640
- }
641
- if (isEnumType(type)) {
642
- return { type: "string", enum: type.values };
643
- }
644
- if (isImportedType(type)) {
645
- return { type: "object", description: `Contract: ${type.name}` };
646
- }
647
- if (isArrayType(type)) {
648
- const itemSchema = jayTypeToJsonSchema(type.itemType);
649
- if (itemSchema) {
650
- return { type: "array", items: itemSchema };
651
- }
652
- return { type: "array" };
653
- }
654
- if (isObjectType(type)) {
655
- const properties = {};
656
- const required = [];
657
- for (const [key, propType] of Object.entries(type.props)) {
658
- const isOpt = isOptionalType(propType);
659
- const schema = jayTypeToJsonSchema(propType);
660
- if (schema) {
661
- properties[key] = schema;
662
- if (!isOpt) {
663
- required.push(key);
664
- }
665
- }
666
- }
667
- return {
668
- type: "object",
669
- properties,
670
- ...required.length > 0 && { required }
671
- };
672
- }
673
- return null;
1072
+ function clearActionRegistry() {
1073
+ actionRegistry.clear();
674
1074
  }
675
- var RuntimeMode = /* @__PURE__ */ ((RuntimeMode2) => {
676
- RuntimeMode2["MainTrusted"] = "mainTrusted";
677
- RuntimeMode2["MainSandbox"] = "mainSandbox";
678
- RuntimeMode2["WorkerTrusted"] = "workerTrusted";
679
- RuntimeMode2["WorkerSandbox"] = "workerSandbox";
680
- return RuntimeMode2;
681
- })(RuntimeMode || {});
682
- const TS_EXTENSION = ".ts";
683
- const JAY_QUERY_PREFIX = "?jay-";
684
- [
685
- // Build environments
686
- {
687
- pattern: `${JAY_QUERY_PREFIX}${"client"}`,
688
- buildEnv: "client"
689
- /* Client */
690
- },
691
- {
692
- pattern: `${JAY_QUERY_PREFIX}${"server"}`,
693
- buildEnv: "server"
694
- /* Server */
695
- },
696
- // Runtime modes (with .ts suffix)
697
- {
698
- pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.MainSandbox}${TS_EXTENSION}`,
699
- runtimeMode: RuntimeMode.MainSandbox
700
- },
701
- {
702
- pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerTrusted}${TS_EXTENSION}`,
703
- runtimeMode: RuntimeMode.WorkerTrusted
704
- },
705
- {
706
- pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerSandbox}${TS_EXTENSION}`,
707
- runtimeMode: RuntimeMode.WorkerSandbox
708
- },
709
- // Runtime modes (without .ts suffix)
710
- {
711
- pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.MainSandbox}`,
712
- runtimeMode: RuntimeMode.MainSandbox
713
- },
714
- {
715
- pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerTrusted}`,
716
- runtimeMode: RuntimeMode.WorkerTrusted
717
- },
718
- {
719
- pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerSandbox}`,
720
- runtimeMode: RuntimeMode.WorkerSandbox
721
- }
722
- ];
723
- createRequire(import.meta.url);
724
- function normalizeActionEntry(entry) {
725
- if (typeof entry === "string") {
726
- return { name: entry };
727
- }
728
- return { name: entry.name, action: entry.action };
1075
+ async function executeAction(actionName, input) {
1076
+ return actionRegistry.execute(actionName, input);
729
1077
  }
730
- function loadPluginManifest(pluginDir) {
731
- const pluginYamlPath = path$1.join(pluginDir, "plugin.yaml");
732
- if (!fs$2.existsSync(pluginYamlPath)) {
733
- return null;
734
- }
735
- try {
736
- const yamlContent = fs$2.readFileSync(pluginYamlPath, "utf-8");
737
- return YAML.parse(yamlContent);
738
- } catch (error) {
739
- return null;
740
- }
1078
+ function getActionCacheHeaders(actionName) {
1079
+ return actionRegistry.getCacheHeaders(actionName);
741
1080
  }
742
- const s = createRequire(import.meta.url), e = s("typescript");
743
- new Proxy(e, {
744
- get(t, r) {
745
- return t[r];
746
- }
747
- });
748
1081
  function parseActionMetadata(yamlContent, fileName) {
749
1082
  try {
750
1083
  const parsed = parseAction(yamlContent, fileName);
@@ -1352,16 +1685,8 @@ function preparePluginClientInits(plugins) {
1352
1685
  };
1353
1686
  });
1354
1687
  }
1355
- function makeCacheKey(jayHtmlPath, params) {
1356
- const sortedParams = Object.keys(params).sort().reduce(
1357
- (acc, key) => {
1358
- acc[key] = params[key];
1359
- return acc;
1360
- },
1361
- {}
1362
- );
1363
- return `${jayHtmlPath}:${JSON.stringify(sortedParams)}`;
1364
- }
1688
+ const CACHE_TAG_START = '<script type="application/jay-cache">';
1689
+ const CACHE_TAG_END = "<\/script>";
1365
1690
  function hashParams(params) {
1366
1691
  const sortedParams = Object.keys(params).sort().reduce(
1367
1692
  (acc, key) => {
@@ -1375,81 +1700,126 @@ function hashParams(params) {
1375
1700
  return "";
1376
1701
  return "_" + crypto.createHash("md5").update(json).digest("hex").substring(0, 8);
1377
1702
  }
1703
+ function embedCacheMetadata(jayHtmlContent, slowViewState, carryForward, sourcePath) {
1704
+ const metadata = JSON.stringify({ slowViewState, carryForward, sourcePath });
1705
+ const cacheTag = `${CACHE_TAG_START}${metadata}${CACHE_TAG_END}`;
1706
+ const headMatch = jayHtmlContent.match(/<head[^>]*>/i);
1707
+ if (headMatch) {
1708
+ const insertPos = headMatch.index + headMatch[0].length;
1709
+ return jayHtmlContent.substring(0, insertPos) + "\n" + cacheTag + jayHtmlContent.substring(insertPos);
1710
+ }
1711
+ return `${cacheTag}
1712
+ ${jayHtmlContent}`;
1713
+ }
1714
+ function extractCacheMetadata(fileContent) {
1715
+ const startIdx = fileContent.indexOf(CACHE_TAG_START);
1716
+ if (startIdx === -1)
1717
+ return void 0;
1718
+ const jsonStart = startIdx + CACHE_TAG_START.length;
1719
+ const endIdx = fileContent.indexOf(CACHE_TAG_END, jsonStart);
1720
+ if (endIdx === -1)
1721
+ return void 0;
1722
+ const jsonStr = fileContent.substring(jsonStart, endIdx);
1723
+ const metadata = JSON.parse(jsonStr);
1724
+ const tagEnd = endIdx + CACHE_TAG_END.length;
1725
+ const afterTag = fileContent[tagEnd] === "\n" ? tagEnd + 1 : tagEnd;
1726
+ const content = fileContent.substring(0, startIdx) + fileContent.substring(afterTag);
1727
+ return {
1728
+ content,
1729
+ slowViewState: metadata.slowViewState,
1730
+ carryForward: metadata.carryForward,
1731
+ sourcePath: metadata.sourcePath
1732
+ };
1733
+ }
1378
1734
  class SlowRenderCache {
1379
1735
  /**
1380
1736
  * @param cacheDir - Directory where pre-rendered jay-html files are stored
1381
1737
  * @param pagesRoot - Root directory of the pages (for relative path calculation)
1382
1738
  */
1383
1739
  constructor(cacheDir, pagesRoot) {
1384
- __publicField(this, "cache", /* @__PURE__ */ new Map());
1385
- __publicField(this, "pathToKeys", /* @__PURE__ */ new Map());
1740
+ /** Maps source jay-html path set of pre-rendered file paths (for invalidation) */
1741
+ __publicField(this, "pathToFiles", /* @__PURE__ */ new Map());
1386
1742
  __publicField(this, "cacheDir");
1387
1743
  __publicField(this, "pagesRoot");
1388
1744
  this.cacheDir = cacheDir;
1389
1745
  this.pagesRoot = pagesRoot;
1390
1746
  }
1391
1747
  /**
1392
- * Get a cached pre-rendered jay-html entry
1748
+ * Get a cached pre-rendered jay-html entry by reading from disk.
1749
+ * Returns undefined if the cache file doesn't exist or has no metadata tag.
1393
1750
  */
1394
- get(jayHtmlPath, params) {
1395
- const key = makeCacheKey(jayHtmlPath, params);
1396
- return this.cache.get(key);
1751
+ async get(jayHtmlPath, params) {
1752
+ const preRenderedPath = this.computeCachePath(jayHtmlPath, params);
1753
+ let fileContent;
1754
+ try {
1755
+ fileContent = await fs$2.readFile(preRenderedPath, "utf-8");
1756
+ } catch {
1757
+ return void 0;
1758
+ }
1759
+ const extracted = extractCacheMetadata(fileContent);
1760
+ if (!extracted)
1761
+ return void 0;
1762
+ this.trackFile(jayHtmlPath, preRenderedPath);
1763
+ return {
1764
+ preRenderedPath,
1765
+ preRenderedContent: extracted.content,
1766
+ slowViewState: extracted.slowViewState,
1767
+ carryForward: extracted.carryForward,
1768
+ sourcePath: extracted.sourcePath
1769
+ };
1397
1770
  }
1398
1771
  /**
1399
- * Store a pre-rendered jay-html entry in the cache.
1400
- * Writes the pre-rendered content to disk and stores metadata in memory.
1772
+ * Store a pre-rendered jay-html entry.
1773
+ * Embeds metadata as a <script> tag and writes to disk.
1774
+ * Returns the full cache entry with stripped content.
1401
1775
  */
1402
1776
  async set(jayHtmlPath, params, preRenderedJayHtml, slowViewState, carryForward) {
1403
- const key = makeCacheKey(jayHtmlPath, params);
1404
- const relativePath = path__default.relative(this.pagesRoot, jayHtmlPath);
1405
- const dir = path__default.dirname(relativePath);
1406
- const basename = path__default.basename(relativePath, ".jay-html");
1407
- const paramsHash = hashParams(params);
1408
- const cacheFileName = `${basename}${paramsHash}.jay-html`;
1409
- const preRenderedPath = path__default.join(this.cacheDir, dir, cacheFileName);
1410
- await fs$1.mkdir(path__default.dirname(preRenderedPath), { recursive: true });
1411
- await fs$1.writeFile(preRenderedPath, preRenderedJayHtml, "utf-8");
1412
- if (!this.pathToKeys.has(jayHtmlPath)) {
1413
- this.pathToKeys.set(jayHtmlPath, /* @__PURE__ */ new Set());
1414
- }
1415
- this.pathToKeys.get(jayHtmlPath).add(key);
1416
- const entry = {
1777
+ const preRenderedPath = this.computeCachePath(jayHtmlPath, params);
1778
+ const fileContent = embedCacheMetadata(
1779
+ preRenderedJayHtml,
1780
+ slowViewState,
1781
+ carryForward,
1782
+ jayHtmlPath
1783
+ );
1784
+ await fs$2.mkdir(path__default.dirname(preRenderedPath), { recursive: true });
1785
+ await fs$2.writeFile(preRenderedPath, fileContent, "utf-8");
1786
+ this.trackFile(jayHtmlPath, preRenderedPath);
1787
+ return {
1417
1788
  preRenderedPath,
1789
+ preRenderedContent: preRenderedJayHtml,
1418
1790
  slowViewState,
1419
1791
  carryForward,
1420
- createdAt: Date.now(),
1421
1792
  sourcePath: jayHtmlPath
1422
1793
  };
1423
- this.cache.set(key, entry);
1424
- return preRenderedPath;
1425
1794
  }
1426
1795
  /**
1427
1796
  * Check if a pre-rendered entry exists for the given path and params
1428
1797
  */
1429
- has(jayHtmlPath, params) {
1430
- const key = makeCacheKey(jayHtmlPath, params);
1431
- return this.cache.has(key);
1798
+ async has(jayHtmlPath, params) {
1799
+ const preRenderedPath = this.computeCachePath(jayHtmlPath, params);
1800
+ try {
1801
+ await fs$2.access(preRenderedPath);
1802
+ return true;
1803
+ } catch {
1804
+ return false;
1805
+ }
1432
1806
  }
1433
1807
  /**
1434
1808
  * Invalidate all cached entries for a given jay-html source path.
1435
- * This is called when the source file changes.
1436
- * Also deletes the cached files from disk.
1809
+ * Deletes cached files from disk.
1437
1810
  */
1438
1811
  async invalidate(jayHtmlPath) {
1439
- const keys = this.pathToKeys.get(jayHtmlPath);
1440
- if (keys) {
1441
- for (const key of keys) {
1442
- const entry = this.cache.get(key);
1443
- if (entry) {
1444
- try {
1445
- await fs$1.unlink(entry.preRenderedPath);
1446
- } catch {
1447
- }
1812
+ const files = this.pathToFiles.get(jayHtmlPath);
1813
+ if (files) {
1814
+ for (const filePath of files) {
1815
+ try {
1816
+ await fs$2.unlink(filePath);
1817
+ } catch {
1448
1818
  }
1449
- this.cache.delete(key);
1450
1819
  }
1451
- this.pathToKeys.delete(jayHtmlPath);
1820
+ this.pathToFiles.delete(jayHtmlPath);
1452
1821
  }
1822
+ await this.scanAndDeleteCacheFiles(jayHtmlPath);
1453
1823
  }
1454
1824
  /**
1455
1825
  * Invalidate all entries that depend on a changed file.
@@ -1477,26 +1847,67 @@ class SlowRenderCache {
1477
1847
  * Clear all cached entries and delete cached files from disk
1478
1848
  */
1479
1849
  async clear() {
1480
- for (const entry of this.cache.values()) {
1481
- try {
1482
- await fs$1.unlink(entry.preRenderedPath);
1483
- } catch {
1850
+ for (const files of this.pathToFiles.values()) {
1851
+ for (const filePath of files) {
1852
+ try {
1853
+ await fs$2.unlink(filePath);
1854
+ } catch {
1855
+ }
1484
1856
  }
1485
1857
  }
1486
- this.cache.clear();
1487
- this.pathToKeys.clear();
1858
+ this.pathToFiles.clear();
1859
+ try {
1860
+ await fs$2.rm(this.cacheDir, { recursive: true, force: true });
1861
+ } catch {
1862
+ }
1488
1863
  }
1489
1864
  /**
1490
- * Get the number of cached entries
1865
+ * Get all cached jay-html paths (for debugging/monitoring)
1491
1866
  */
1492
- get size() {
1493
- return this.cache.size;
1867
+ getCachedPaths() {
1868
+ return Array.from(this.pathToFiles.keys());
1494
1869
  }
1495
1870
  /**
1496
- * Get all cached jay-html paths (for debugging/monitoring)
1871
+ * Compute the cache file path for a given jay-html path and params.
1497
1872
  */
1498
- getCachedPaths() {
1499
- return Array.from(this.pathToKeys.keys());
1873
+ computeCachePath(jayHtmlPath, params) {
1874
+ const relativePath = path__default.relative(this.pagesRoot, jayHtmlPath);
1875
+ const dir = path__default.dirname(relativePath);
1876
+ const basename = path__default.basename(relativePath, ".jay-html");
1877
+ const paramsHash = hashParams(params);
1878
+ const cacheFileName = `${basename}${paramsHash}.jay-html`;
1879
+ return path__default.join(this.cacheDir, dir, cacheFileName);
1880
+ }
1881
+ /**
1882
+ * Track a pre-rendered file path for invalidation.
1883
+ */
1884
+ trackFile(jayHtmlPath, preRenderedPath) {
1885
+ if (!this.pathToFiles.has(jayHtmlPath)) {
1886
+ this.pathToFiles.set(jayHtmlPath, /* @__PURE__ */ new Set());
1887
+ }
1888
+ this.pathToFiles.get(jayHtmlPath).add(preRenderedPath);
1889
+ }
1890
+ /**
1891
+ * Scan the cache directory for files matching a route and delete them.
1892
+ * Handles the startup case where pathToFiles is not populated from a previous session.
1893
+ */
1894
+ async scanAndDeleteCacheFiles(jayHtmlPath) {
1895
+ const relativePath = path__default.relative(this.pagesRoot, jayHtmlPath);
1896
+ const dir = path__default.dirname(relativePath);
1897
+ const basename = path__default.basename(relativePath, ".jay-html");
1898
+ const cacheSubDir = path__default.join(this.cacheDir, dir);
1899
+ try {
1900
+ const files = await fs$2.readdir(cacheSubDir);
1901
+ for (const file of files) {
1902
+ if (file.startsWith(basename) && file.endsWith(".jay-html")) {
1903
+ try {
1904
+ await fs$2.unlink(path__default.join(cacheSubDir, file));
1905
+ } catch {
1906
+ }
1907
+ }
1908
+ }
1909
+ } catch {
1910
+ }
1500
1911
  }
1501
1912
  }
1502
1913
  const require2 = createRequire(import.meta.url);
@@ -1640,13 +2051,12 @@ function getJayStackVersion() {
1640
2051
  async function materializeContracts(options, services = /* @__PURE__ */ new Map()) {
1641
2052
  const {
1642
2053
  projectRoot,
1643
- outputDir = path.join(projectRoot, "build", "materialized-contracts"),
2054
+ outputDir = path.join(projectRoot, "agent-kit", "materialized-contracts"),
1644
2055
  dynamicOnly = false,
1645
2056
  pluginFilter,
1646
2057
  verbose = false,
1647
2058
  viteServer
1648
2059
  } = options;
1649
- const contracts = [];
1650
2060
  const pluginsIndexMap = /* @__PURE__ */ new Map();
1651
2061
  let staticCount = 0;
1652
2062
  let dynamicCount = 0;
@@ -1670,6 +2080,14 @@ async function materializeContracts(options, services = /* @__PURE__ */ new Map(
1670
2080
  📦 Processing plugin: ${plugin.name}`);
1671
2081
  }
1672
2082
  const { manifest } = plugin;
2083
+ const pluginRelPath = path.relative(projectRoot, plugin.pluginPath);
2084
+ if (!pluginsIndexMap.has(plugin.name)) {
2085
+ pluginsIndexMap.set(plugin.name, {
2086
+ path: "./" + pluginRelPath.replace(/\\/g, "/"),
2087
+ contracts: [],
2088
+ actions: []
2089
+ });
2090
+ }
1673
2091
  if (!dynamicOnly && manifest.contracts) {
1674
2092
  for (const contract of manifest.contracts) {
1675
2093
  const contractPath = resolveStaticContractPath(
@@ -1678,27 +2096,12 @@ async function materializeContracts(options, services = /* @__PURE__ */ new Map(
1678
2096
  projectRoot
1679
2097
  );
1680
2098
  const relativePath = path.relative(projectRoot, contractPath);
1681
- const entry = {
1682
- plugin: plugin.name,
1683
- name: contract.name,
1684
- type: "static",
1685
- path: "./" + relativePath
1686
- };
1687
- contracts.push(entry);
1688
- staticCount++;
1689
- const pluginRelPath = path.relative(projectRoot, plugin.pluginPath);
1690
- if (!pluginsIndexMap.has(plugin.name)) {
1691
- pluginsIndexMap.set(plugin.name, {
1692
- path: "./" + pluginRelPath.replace(/\\/g, "/"),
1693
- contracts: [],
1694
- actions: []
1695
- });
1696
- }
1697
2099
  pluginsIndexMap.get(plugin.name).contracts.push({
1698
2100
  name: contract.name,
1699
2101
  type: "static",
1700
2102
  path: "./" + relativePath
1701
2103
  });
2104
+ staticCount++;
1702
2105
  if (verbose) {
1703
2106
  getLogger().info(` 📄 Static: ${contract.name}`);
1704
2107
  }
@@ -1728,28 +2131,14 @@ async function materializeContracts(options, services = /* @__PURE__ */ new Map(
1728
2131
  const filePath = path.join(pluginOutputDir, fileName);
1729
2132
  fs.writeFileSync(filePath, generated.yaml, "utf-8");
1730
2133
  const relativePath = path.relative(projectRoot, filePath);
1731
- const dynEntry = {
1732
- plugin: plugin.name,
2134
+ const contractEntry = {
1733
2135
  name: fullName,
1734
2136
  type: "dynamic",
1735
2137
  path: "./" + relativePath,
1736
- metadata: generated.metadata
2138
+ ...generated.metadata && { metadata: generated.metadata }
1737
2139
  };
1738
- contracts.push(dynEntry);
2140
+ pluginsIndexMap.get(plugin.name).contracts.push(contractEntry);
1739
2141
  dynamicCount++;
1740
- const pluginRelPath = path.relative(projectRoot, plugin.pluginPath);
1741
- if (!pluginsIndexMap.has(plugin.name)) {
1742
- pluginsIndexMap.set(plugin.name, {
1743
- path: "./" + pluginRelPath.replace(/\\/g, "/"),
1744
- contracts: [],
1745
- actions: []
1746
- });
1747
- }
1748
- pluginsIndexMap.get(plugin.name).contracts.push({
1749
- name: fullName,
1750
- type: "dynamic",
1751
- path: "./" + relativePath
1752
- });
1753
2142
  if (verbose) {
1754
2143
  getLogger().info(` ⚡ Materialized: ${fullName}`);
1755
2144
  }
@@ -1778,14 +2167,6 @@ async function materializeContracts(options, services = /* @__PURE__ */ new Map(
1778
2167
  const metadata = loadActionMetadata(metadataFilePath);
1779
2168
  if (!metadata)
1780
2169
  continue;
1781
- const pluginRelPath = path.relative(projectRoot, plugin.pluginPath);
1782
- if (!pluginsIndexMap.has(plugin.name)) {
1783
- pluginsIndexMap.set(plugin.name, {
1784
- path: "./" + pluginRelPath.replace(/\\/g, "/"),
1785
- contracts: [],
1786
- actions: []
1787
- });
1788
- }
1789
2170
  const actionRelPath = path.relative(projectRoot, metadataFilePath);
1790
2171
  pluginsIndexMap.get(plugin.name).actions.push({
1791
2172
  name: metadata.name,
@@ -1798,17 +2179,8 @@ async function materializeContracts(options, services = /* @__PURE__ */ new Map(
1798
2179
  }
1799
2180
  }
1800
2181
  }
1801
- const index = {
1802
- materialized_at: (/* @__PURE__ */ new Date()).toISOString(),
1803
- jay_stack_version: getJayStackVersion(),
1804
- contracts
1805
- };
1806
- fs.mkdirSync(outputDir, { recursive: true });
1807
- const indexPath = path.join(outputDir, "contracts-index.yaml");
1808
- fs.writeFileSync(indexPath, YAML.stringify(index), "utf-8");
1809
2182
  const pluginsIndex = {
1810
- materialized_at: index.materialized_at,
1811
- jay_stack_version: index.jay_stack_version,
2183
+ jay_stack_version: getJayStackVersion(),
1812
2184
  plugins: Array.from(pluginsIndexMap.entries()).map(([name, data]) => ({
1813
2185
  name,
1814
2186
  path: data.path,
@@ -1816,15 +2188,16 @@ async function materializeContracts(options, services = /* @__PURE__ */ new Map(
1816
2188
  ...data.actions.length > 0 && { actions: data.actions }
1817
2189
  }))
1818
2190
  };
1819
- const pluginsIndexPath = path.join(outputDir, "plugins-index.yaml");
2191
+ fs.mkdirSync(outputDir, { recursive: true });
2192
+ const agentKitDir = path.dirname(outputDir);
2193
+ const pluginsIndexPath = path.join(agentKitDir, "plugins-index.yaml");
1820
2194
  fs.writeFileSync(pluginsIndexPath, YAML.stringify(pluginsIndex), "utf-8");
1821
2195
  if (verbose) {
1822
2196
  getLogger().info(`
1823
- Contracts index written to: ${indexPath}`);
1824
- getLogger().info(`✅ Plugins index written to: ${pluginsIndexPath}`);
2197
+ Plugins index written to: ${pluginsIndexPath}`);
1825
2198
  }
1826
2199
  return {
1827
- index,
2200
+ pluginsIndex,
1828
2201
  staticCount,
1829
2202
  dynamicCount,
1830
2203
  outputDir
@@ -1832,7 +2205,7 @@ async function materializeContracts(options, services = /* @__PURE__ */ new Map(
1832
2205
  }
1833
2206
  async function listContracts(options) {
1834
2207
  const { projectRoot, dynamicOnly = false, pluginFilter } = options;
1835
- const contracts = [];
2208
+ const pluginsMap = /* @__PURE__ */ new Map();
1836
2209
  const plugins = await scanPlugins({
1837
2210
  projectRoot,
1838
2211
  includeDevDeps: true
@@ -1841,6 +2214,13 @@ async function listContracts(options) {
1841
2214
  if (pluginFilter && plugin.name !== pluginFilter && pluginKey !== pluginFilter)
1842
2215
  continue;
1843
2216
  const { manifest } = plugin;
2217
+ const pluginRelPath = path.relative(projectRoot, plugin.pluginPath);
2218
+ if (!pluginsMap.has(plugin.name)) {
2219
+ pluginsMap.set(plugin.name, {
2220
+ path: "./" + pluginRelPath.replace(/\\/g, "/"),
2221
+ contracts: []
2222
+ });
2223
+ }
1844
2224
  if (!dynamicOnly && manifest.contracts) {
1845
2225
  for (const contract of manifest.contracts) {
1846
2226
  const contractPath = resolveStaticContractPath(
@@ -1849,8 +2229,7 @@ async function listContracts(options) {
1849
2229
  projectRoot
1850
2230
  );
1851
2231
  const relativePath = path.relative(projectRoot, contractPath);
1852
- contracts.push({
1853
- plugin: plugin.name,
2232
+ pluginsMap.get(plugin.name).contracts.push({
1854
2233
  name: contract.name,
1855
2234
  type: "static",
1856
2235
  path: "./" + relativePath
@@ -1860,8 +2239,7 @@ async function listContracts(options) {
1860
2239
  if (manifest.dynamic_contracts) {
1861
2240
  const dynamicConfigs = Array.isArray(manifest.dynamic_contracts) ? manifest.dynamic_contracts : [manifest.dynamic_contracts];
1862
2241
  for (const config of dynamicConfigs) {
1863
- contracts.push({
1864
- plugin: plugin.name,
2242
+ pluginsMap.get(plugin.name).contracts.push({
1865
2243
  name: `${config.prefix}/*`,
1866
2244
  type: "dynamic",
1867
2245
  path: "(run materialization to generate)"
@@ -1870,9 +2248,12 @@ async function listContracts(options) {
1870
2248
  }
1871
2249
  }
1872
2250
  return {
1873
- materialized_at: (/* @__PURE__ */ new Date()).toISOString(),
1874
2251
  jay_stack_version: getJayStackVersion(),
1875
- contracts
2252
+ plugins: Array.from(pluginsMap.entries()).map(([name, data]) => ({
2253
+ name,
2254
+ path: data.path,
2255
+ contracts: data.contracts
2256
+ }))
1876
2257
  };
1877
2258
  }
1878
2259
  async function discoverPluginsWithSetup(options) {
@@ -2007,9 +2388,12 @@ export {
2007
2388
  DevSlowlyChangingPhase,
2008
2389
  SlowRenderCache,
2009
2390
  actionRegistry,
2391
+ buildAutomationWrap,
2392
+ buildScriptFragments,
2010
2393
  clearActionRegistry,
2011
2394
  clearClientInitData,
2012
2395
  clearLifecycleCallbacks,
2396
+ clearServerElementCache,
2013
2397
  clearServiceRegistry,
2014
2398
  discoverAllPluginActions,
2015
2399
  discoverAndRegisterActions,
@@ -2022,6 +2406,7 @@ export {
2022
2406
  executePluginServerInits,
2023
2407
  executePluginSetup,
2024
2408
  generateClientScript,
2409
+ generateSSRPageHtml,
2025
2410
  getActionCacheHeaders,
2026
2411
  getClientInitData,
2027
2412
  getClientInitDataForKey,
@@ -2031,6 +2416,7 @@ export {
2031
2416
  getServiceRegistry,
2032
2417
  hasAction,
2033
2418
  hasService,
2419
+ invalidateServerElementCache,
2034
2420
  listContracts,
2035
2421
  loadActionMetadata,
2036
2422
  loadPageParts,