@jay-framework/stack-server-runtime 0.13.0 → 0.14.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 +164 -106
  2. package/dist/index.js +789 -357
  3. package/package.json +13 -12
package/dist/index.js CHANGED
@@ -5,19 +5,19 @@ var __publicField = (obj, key, value) => {
5
5
  return value;
6
6
  };
7
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";
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) {
@@ -86,32 +86,42 @@ function clearClientInitData() {
86
86
  function isLeftSideParamsSubsetOfRightSideParams(left, right) {
87
87
  return Object.keys(left).reduce((prev, curr) => prev && left[curr] === right[curr], true);
88
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
89
  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
90
+ constructor() {
91
+ /** Cached loadParams results per route (jayHtmlPath → array of UrlParams[] per part) */
92
+ __publicField(this, "loadParamsCache", /* @__PURE__ */ new Map());
93
+ }
94
+ async runSlowlyForPage(pageParams, pageProps, parts, discoveredInstances, headlessInstanceComponents, jayHtmlPath) {
95
+ const loadParamsParts = parts.filter((p) => p.compDefinition.loadParams);
96
+ if (loadParamsParts.length > 0) {
97
+ let cachedParamsArray = jayHtmlPath ? this.loadParamsCache.get(jayHtmlPath) : void 0;
98
+ if (!cachedParamsArray) {
99
+ cachedParamsArray = [];
100
+ for (const part of loadParamsParts) {
101
+ const { compDefinition, contractInfo } = part;
102
+ const services = resolveServices(compDefinition.services);
103
+ const loadParamsArgs = contractInfo ? [
104
+ ...services,
105
+ {
106
+ contractName: contractInfo.contractName,
107
+ metadata: contractInfo.metadata
108
+ }
109
+ ] : services;
110
+ const compParams = compDefinition.loadParams(loadParamsArgs);
111
+ const collected = [];
112
+ for await (const batch of compParams) {
113
+ collected.push(...batch);
110
114
  }
111
- ] : services;
112
- const compParams = compDefinition.loadParams(loadParamsArgs);
113
- if (!await findMatchingParams(pageParams, compParams))
115
+ cachedParamsArray.push(collected);
116
+ }
117
+ if (jayHtmlPath) {
118
+ this.loadParamsCache.set(jayHtmlPath, cachedParamsArray);
119
+ }
120
+ }
121
+ for (const partParams of cachedParamsArray) {
122
+ if (!partParams.some((p) => isLeftSideParamsSubsetOfRightSideParams(pageParams, p))) {
114
123
  return notFound();
124
+ }
115
125
  }
116
126
  }
117
127
  let slowlyViewState = {};
@@ -144,15 +154,264 @@ class DevSlowlyChangingPhase {
144
154
  return slowlyRenderedPart;
145
155
  }
146
156
  }
157
+ if (discoveredInstances && discoveredInstances.length > 0 && headlessInstanceComponents) {
158
+ const componentByContractName = /* @__PURE__ */ new Map();
159
+ for (const comp of headlessInstanceComponents) {
160
+ componentByContractName.set(comp.contractName, comp);
161
+ }
162
+ const instancePhaseData = {
163
+ discovered: [],
164
+ carryForwards: {}
165
+ };
166
+ const instanceSlowViewStates = {};
167
+ const instanceResolvedData = [];
168
+ for (const instance of discoveredInstances) {
169
+ const comp = componentByContractName.get(instance.contractName);
170
+ if (!comp)
171
+ continue;
172
+ const coordKey = instance.coordinate.join("/");
173
+ const contractProps = comp.contract?.props ?? [];
174
+ const normalizedProps = {};
175
+ for (const [key, value] of Object.entries(instance.props)) {
176
+ const match = contractProps.find(
177
+ (p) => p.name.toLowerCase() === key.toLowerCase()
178
+ );
179
+ normalizedProps[match ? match.name : key] = value;
180
+ }
181
+ instancePhaseData.discovered.push({
182
+ contractName: instance.contractName,
183
+ props: normalizedProps,
184
+ coordinate: instance.coordinate
185
+ });
186
+ if (comp.compDefinition.slowlyRender) {
187
+ const services = resolveServices(comp.compDefinition.services);
188
+ const slowResult = await comp.compDefinition.slowlyRender(
189
+ normalizedProps,
190
+ ...services
191
+ );
192
+ if (slowResult.kind === "PhaseOutput") {
193
+ instanceSlowViewStates[coordKey] = slowResult.rendered;
194
+ instancePhaseData.carryForwards[coordKey] = slowResult.carryForward;
195
+ instanceResolvedData.push({
196
+ coordinate: instance.coordinate,
197
+ contract: comp.contract,
198
+ slowViewState: slowResult.rendered
199
+ });
200
+ }
201
+ }
202
+ }
203
+ carryForward.__instances = instancePhaseData;
204
+ carryForward.__instanceSlowViewStates = instanceSlowViewStates;
205
+ carryForward.__instanceResolvedData = instanceResolvedData;
206
+ }
147
207
  return partialRender(slowlyViewState, carryForward);
148
208
  }
209
+ /**
210
+ * Invalidate the cached loadParams result for a given route.
211
+ * Called when source files change (page.ts, .jay-contract, .jay-html).
212
+ */
213
+ invalidateLoadParamsCache(jayHtmlPath) {
214
+ this.loadParamsCache.delete(jayHtmlPath);
215
+ }
149
216
  }
150
217
  async function runLoadParams(compDefinition, services) {
151
218
  compDefinition.loadParams(services);
152
219
  }
153
220
  function runSlowlyChangingRender(compDefinition) {
154
221
  }
155
- async function renderFastChangingData(pageParams, pageProps, carryForward, parts) {
222
+ var __defProp2 = Object.defineProperty;
223
+ var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
224
+ var __publicField2 = (obj, key, value) => {
225
+ __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
226
+ return value;
227
+ };
228
+ class JayAtomicType {
229
+ constructor(name) {
230
+ __publicField2(this, "kind", 0);
231
+ this.name = name;
232
+ }
233
+ }
234
+ new JayAtomicType("string");
235
+ new JayAtomicType("number");
236
+ new JayAtomicType("boolean");
237
+ new JayAtomicType("Date");
238
+ new JayAtomicType("Unknown");
239
+ class JayObjectType {
240
+ constructor(name, props) {
241
+ __publicField2(this, "kind", 8);
242
+ this.name = name;
243
+ this.props = props;
244
+ }
245
+ }
246
+ new JayObjectType("Error", {
247
+ message: new JayAtomicType("string"),
248
+ name: new JayAtomicType("string"),
249
+ stack: new JayAtomicType("string")
250
+ });
251
+ function isOptionalType(aType) {
252
+ return aType.kind === 13;
253
+ }
254
+ function isAtomicType(aType) {
255
+ return aType.kind === 0;
256
+ }
257
+ function isEnumType(aType) {
258
+ return aType.kind === 2;
259
+ }
260
+ function isImportedType(aType) {
261
+ return aType.kind === 4;
262
+ }
263
+ function isObjectType(aType) {
264
+ return aType.kind === 8;
265
+ }
266
+ function isArrayType(aType) {
267
+ return aType.kind === 9;
268
+ }
269
+ function jayTypeToJsonSchema(type) {
270
+ if (isOptionalType(type)) {
271
+ return jayTypeToJsonSchema(type.innerType);
272
+ }
273
+ if (isAtomicType(type)) {
274
+ const name = type.name.toLowerCase();
275
+ if (name === "string" || name === "number" || name === "boolean") {
276
+ return { type: name };
277
+ }
278
+ return { type: "string" };
279
+ }
280
+ if (isEnumType(type)) {
281
+ return { type: "string", enum: type.values };
282
+ }
283
+ if (isImportedType(type)) {
284
+ return { type: "object", description: `Contract: ${type.name}` };
285
+ }
286
+ if (isArrayType(type)) {
287
+ const itemSchema = jayTypeToJsonSchema(type.itemType);
288
+ if (itemSchema) {
289
+ return { type: "array", items: itemSchema };
290
+ }
291
+ return { type: "array" };
292
+ }
293
+ if (isObjectType(type)) {
294
+ const properties = {};
295
+ const required = [];
296
+ for (const [key, propType] of Object.entries(type.props)) {
297
+ const isOpt = isOptionalType(propType);
298
+ const schema = jayTypeToJsonSchema(propType);
299
+ if (schema) {
300
+ properties[key] = schema;
301
+ if (!isOpt) {
302
+ required.push(key);
303
+ }
304
+ }
305
+ }
306
+ return {
307
+ type: "object",
308
+ properties,
309
+ ...required.length > 0 && { required }
310
+ };
311
+ }
312
+ return null;
313
+ }
314
+ var RuntimeMode = /* @__PURE__ */ ((RuntimeMode2) => {
315
+ RuntimeMode2["MainTrusted"] = "mainTrusted";
316
+ RuntimeMode2["MainSandbox"] = "mainSandbox";
317
+ RuntimeMode2["WorkerTrusted"] = "workerTrusted";
318
+ RuntimeMode2["WorkerSandbox"] = "workerSandbox";
319
+ return RuntimeMode2;
320
+ })(RuntimeMode || {});
321
+ const TS_EXTENSION = ".ts";
322
+ const JAY_QUERY_PREFIX = "?jay-";
323
+ const JAY_QUERY_HYDRATE = `${JAY_QUERY_PREFIX}hydrate`;
324
+ [
325
+ // Hydrate target
326
+ {
327
+ pattern: JAY_QUERY_HYDRATE,
328
+ buildEnv: "client",
329
+ isHydrate: true
330
+ },
331
+ // Build environments
332
+ {
333
+ pattern: `${JAY_QUERY_PREFIX}${"client"}`,
334
+ buildEnv: "client"
335
+ /* Client */
336
+ },
337
+ {
338
+ pattern: `${JAY_QUERY_PREFIX}${"server"}`,
339
+ buildEnv: "server"
340
+ /* Server */
341
+ },
342
+ // Runtime modes (with .ts suffix)
343
+ {
344
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.MainSandbox}${TS_EXTENSION}`,
345
+ runtimeMode: RuntimeMode.MainSandbox
346
+ },
347
+ {
348
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerTrusted}${TS_EXTENSION}`,
349
+ runtimeMode: RuntimeMode.WorkerTrusted
350
+ },
351
+ {
352
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerSandbox}${TS_EXTENSION}`,
353
+ runtimeMode: RuntimeMode.WorkerSandbox
354
+ },
355
+ // Runtime modes (without .ts suffix)
356
+ {
357
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.MainSandbox}`,
358
+ runtimeMode: RuntimeMode.MainSandbox
359
+ },
360
+ {
361
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerTrusted}`,
362
+ runtimeMode: RuntimeMode.WorkerTrusted
363
+ },
364
+ {
365
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerSandbox}`,
366
+ runtimeMode: RuntimeMode.WorkerSandbox
367
+ }
368
+ ];
369
+ function checkValidationErrors(withValidations) {
370
+ const { validations } = withValidations;
371
+ if (validations.length > 0) {
372
+ throw new Error(validations.join("\n"));
373
+ }
374
+ return withValidations.val;
375
+ }
376
+ createRequire(import.meta.url);
377
+ function normalizeActionEntry(entry) {
378
+ if (typeof entry === "string") {
379
+ return { name: entry };
380
+ }
381
+ return { name: entry.name, action: entry.action };
382
+ }
383
+ function loadPluginManifest(pluginDir) {
384
+ const pluginYamlPath = path$1.join(pluginDir, "plugin.yaml");
385
+ if (!fs$1.existsSync(pluginYamlPath)) {
386
+ return null;
387
+ }
388
+ try {
389
+ const yamlContent = fs$1.readFileSync(pluginYamlPath, "utf-8");
390
+ return YAML.parse(yamlContent);
391
+ } catch (error) {
392
+ return null;
393
+ }
394
+ }
395
+ function computeForEachInstanceKey(trackByValue, coordinateSuffix) {
396
+ return [trackByValue, coordinateSuffix].toString();
397
+ }
398
+ const s = createRequire(import.meta.url), e = s("typescript");
399
+ new Proxy(e, {
400
+ get(t, r) {
401
+ return t[r];
402
+ }
403
+ });
404
+ function resolvePathValue(obj, path2) {
405
+ return path2.split(".").reduce((current, segment) => current?.[segment], obj);
406
+ }
407
+ function resolveBinding(binding, item) {
408
+ const match = binding.match(/^\{(.+)\}$/);
409
+ if (match) {
410
+ return String(item[match[1]] ?? "");
411
+ }
412
+ return binding;
413
+ }
414
+ async function renderFastChangingData(pageParams, pageProps, carryForward, parts, instancePhaseData, forEachInstances, headlessInstanceComponents, mergedSlowViewState) {
156
415
  let fastViewState = {};
157
416
  let fastCarryForward = {};
158
417
  for (const part of parts) {
@@ -185,12 +444,92 @@ async function renderFastChangingData(pageParams, pageProps, carryForward, parts
185
444
  return fastRenderedPart;
186
445
  }
187
446
  }
447
+ const instanceViewStates = {};
448
+ const instanceCarryForwards = {};
449
+ if (instancePhaseData && headlessInstanceComponents) {
450
+ const componentByContractName = /* @__PURE__ */ new Map();
451
+ for (const comp of headlessInstanceComponents) {
452
+ componentByContractName.set(comp.contractName, comp);
453
+ }
454
+ for (const instance of instancePhaseData.discovered) {
455
+ const coordKey = instance.coordinate.join("/");
456
+ const comp = componentByContractName.get(instance.contractName);
457
+ if (!comp || !comp.compDefinition.fastRender)
458
+ continue;
459
+ const services = resolveServices(comp.compDefinition.services);
460
+ const cf = instancePhaseData.carryForwards[coordKey];
461
+ const fastResult = comp.compDefinition.slowlyRender ? await comp.compDefinition.fastRender(instance.props, cf, ...services) : await comp.compDefinition.fastRender(instance.props, ...services);
462
+ if (fastResult.kind === "PhaseOutput") {
463
+ const instanceSlowVS = carryForward?.__instanceSlowViewStates?.[coordKey];
464
+ instanceViewStates[coordKey] = instanceSlowVS ? { ...instanceSlowVS, ...fastResult.rendered } : fastResult.rendered;
465
+ if (fastResult.carryForward) {
466
+ instanceCarryForwards[coordKey] = fastResult.carryForward;
467
+ }
468
+ }
469
+ }
470
+ }
471
+ if (forEachInstances && forEachInstances.length > 0 && headlessInstanceComponents) {
472
+ const componentByContractName = /* @__PURE__ */ new Map();
473
+ for (const comp of headlessInstanceComponents) {
474
+ componentByContractName.set(comp.contractName, comp);
475
+ }
476
+ const mergedForEachVS = { ...mergedSlowViewState || {}, ...fastViewState };
477
+ for (const instance of forEachInstances) {
478
+ const comp = componentByContractName.get(instance.contractName);
479
+ if (!comp)
480
+ continue;
481
+ const items = resolvePathValue(mergedForEachVS, instance.forEachPath);
482
+ if (!Array.isArray(items))
483
+ continue;
484
+ const contractProps = comp.contract?.props ?? [];
485
+ const normalizePropName = (key) => contractProps.find((p) => p.name.toLowerCase() === key.toLowerCase())?.name ?? key;
486
+ for (const item of items) {
487
+ const trackByValue = String(item[instance.trackBy]);
488
+ const props = {};
489
+ for (const [propName, binding] of Object.entries(instance.propBindings)) {
490
+ props[normalizePropName(propName)] = resolveBinding(String(binding), item);
491
+ }
492
+ if (comp.compDefinition.fastRender) {
493
+ const services = resolveServices(comp.compDefinition.services);
494
+ let slowVS = {};
495
+ let cf = {};
496
+ if (comp.compDefinition.slowlyRender) {
497
+ const slowResult = await comp.compDefinition.slowlyRender(
498
+ props,
499
+ ...services
500
+ );
501
+ if (slowResult.kind === "PhaseOutput") {
502
+ slowVS = slowResult.rendered;
503
+ cf = slowResult.carryForward;
504
+ }
505
+ }
506
+ const fastResult = comp.compDefinition.slowlyRender ? await comp.compDefinition.fastRender(props, cf, ...services) : await comp.compDefinition.fastRender(props, ...services);
507
+ if (fastResult.kind === "PhaseOutput") {
508
+ const coord = computeForEachInstanceKey(
509
+ trackByValue,
510
+ instance.coordinateSuffix
511
+ );
512
+ instanceViewStates[coord] = { ...slowVS, ...fastResult.rendered };
513
+ if (fastResult.carryForward) {
514
+ instanceCarryForwards[coord] = fastResult.carryForward;
515
+ }
516
+ }
517
+ }
518
+ }
519
+ }
520
+ }
521
+ if (Object.keys(instanceViewStates).length > 0) {
522
+ fastViewState.__headlessInstances = instanceViewStates;
523
+ }
524
+ if (Object.keys(instanceCarryForwards).length > 0) {
525
+ fastCarryForward.__headlessInstances = instanceCarryForwards;
526
+ }
188
527
  return Promise.resolve(phaseOutput(fastViewState, fastCarryForward));
189
528
  }
190
- function generateClientScript(defaultViewState, fastCarryForward, parts, jayHtmlPath, trackByMap = {}, clientInitData2 = {}, projectInit, pluginInits = [], options = {}) {
529
+ function buildScriptFragments(parts, clientInitData2, projectInit, pluginInits, options) {
191
530
  const { enableAutomation = true, slowViewState } = options;
192
531
  const hasSlowViewState = slowViewState && Object.keys(slowViewState).length > 0;
193
- const imports = parts.length > 0 ? parts.map((part) => part.clientImport).join("\n") + "\n" : "";
532
+ const partImports = parts.length > 0 ? parts.map((part) => part.clientImport).join("\n") + "\n" : "";
194
533
  const compositeParts = parts.length > 0 ? `[
195
534
  ${parts.map((part) => " " + part.clientPart).join(",\n")}
196
535
  ]` : "[]";
@@ -214,7 +553,7 @@ ${parts.map((part) => " " + part.clientPart).join(",\n")}
214
553
  const clientInitExecution = hasClientInit ? `
215
554
  // Plugin client initialization (in dependency order)
216
555
  ${pluginClientInitCalls}
217
-
556
+
218
557
  // Project client initialization
219
558
  ${projectInitCall}
220
559
  ` : "";
@@ -223,7 +562,28 @@ ${parts.map((part) => " " + part.clientPart).join(",\n")}
223
562
  import { deepMergeViewStates } from "@jay-framework/view-state-merge";` : `import { wrapWithAutomation, AUTOMATION_CONTEXT } from "@jay-framework/runtime-automation";
224
563
  import { registerGlobalContext } from "@jay-framework/runtime";` : "";
225
564
  const slowViewStateDecl = enableAutomation && hasSlowViewState ? `const slowViewState = ${JSON.stringify(slowViewState)};` : "";
226
- const automationWrap = enableAutomation ? hasSlowViewState ? `
565
+ return {
566
+ partImports,
567
+ compositeParts,
568
+ pluginClientInitImports,
569
+ projectInitImport,
570
+ clientInitExecution,
571
+ automationImport,
572
+ slowViewStateDecl
573
+ };
574
+ }
575
+ function buildAutomationWrap(options, mode) {
576
+ const { enableAutomation = true, slowViewState } = options;
577
+ const hasSlowViewState = slowViewState && Object.keys(slowViewState).length > 0;
578
+ const appendDom = mode === "client";
579
+ if (!enableAutomation) {
580
+ return appendDom ? `
581
+ target.appendChild(instance.element.dom);` : "";
582
+ }
583
+ const appendLine = appendDom ? `
584
+ target.appendChild(wrapped.element.dom);` : "";
585
+ if (hasSlowViewState) {
586
+ return `
227
587
  // Wrap with automation for dev tooling
228
588
  // Deep merge slow+fast ViewState so automation can see full page state
229
589
  const fullViewState = deepMergeViewStates(slowViewState, {...viewState, ...fastCarryForward}, trackByMap);
@@ -231,14 +591,27 @@ ${parts.map((part) => " " + part.clientPart).join(",\n")}
231
591
  registerGlobalContext(AUTOMATION_CONTEXT, wrapped.automation);
232
592
  window.__jay = window.__jay || {};
233
593
  window.__jay.automation = wrapped.automation;
234
- target.appendChild(wrapped.element.dom);` : `
594
+ window.dispatchEvent(new Event('jay:automation-ready'));${appendLine}`;
595
+ }
596
+ return `
235
597
  // Wrap with automation for dev tooling
236
598
  const wrapped = wrapWithAutomation(instance);
237
599
  registerGlobalContext(AUTOMATION_CONTEXT, wrapped.automation);
238
600
  window.__jay = window.__jay || {};
239
601
  window.__jay.automation = wrapped.automation;
240
- target.appendChild(wrapped.element.dom);` : `
241
- target.appendChild(instance.element.dom);`;
602
+ window.dispatchEvent(new Event('jay:automation-ready'));${appendLine}`;
603
+ }
604
+ function generateClientScript(defaultViewState, fastCarryForward, parts, jayHtmlPath, trackByMap = {}, clientInitData2 = {}, projectInit, pluginInits = [], options = {}) {
605
+ const {
606
+ partImports,
607
+ compositeParts,
608
+ pluginClientInitImports,
609
+ projectInitImport,
610
+ clientInitExecution,
611
+ automationImport,
612
+ slowViewStateDecl
613
+ } = buildScriptFragments(parts, clientInitData2, projectInit, pluginInits, options);
614
+ const automationWrap = buildAutomationWrap(options, "client");
242
615
  return `<!doctype html>
243
616
  <html lang="en">
244
617
  <head>
@@ -254,7 +627,7 @@ ${parts.map((part) => " " + part.clientPart).join(",\n")}
254
627
  ${pluginClientInitImports}
255
628
  ${projectInitImport}
256
629
  import { render } from '${jayHtmlPath}';
257
- ${imports}${slowViewStateDecl}
630
+ ${partImports}${slowViewStateDecl}
258
631
  const viewState = ${JSON.stringify(defaultViewState)};
259
632
  const fastCarryForward = ${JSON.stringify(fastCarryForward)};
260
633
  const trackByMap = ${JSON.stringify(trackByMap)};
@@ -268,9 +641,171 @@ ${automationWrap}
268
641
  </body>
269
642
  </html>`;
270
643
  }
644
+ const serverModuleCache = /* @__PURE__ */ new Map();
645
+ function invalidateServerElementCache(jayHtmlPath) {
646
+ if (serverModuleCache.delete(jayHtmlPath)) {
647
+ getLogger().info(`[SSR] Invalidated server element cache for ${jayHtmlPath}`);
648
+ }
649
+ }
650
+ function clearServerElementCache() {
651
+ serverModuleCache.clear();
652
+ }
653
+ async function generateSSRPageHtml(vite, jayHtmlContent, jayHtmlFilename, jayHtmlDir, viewState, jayHtmlImportPath, parts, carryForward, trackByMap = {}, clientInitData2 = {}, buildFolder, projectRoot, routeDir, tsConfigFilePath, projectInit, pluginInits = [], options = {}) {
654
+ const jayHtmlPath = path__default.join(jayHtmlDir, jayHtmlFilename);
655
+ let cached = serverModuleCache.get(jayHtmlPath);
656
+ if (!cached) {
657
+ cached = await compileAndLoadServerElement(
658
+ vite,
659
+ jayHtmlContent,
660
+ jayHtmlFilename,
661
+ jayHtmlDir,
662
+ buildFolder,
663
+ projectRoot,
664
+ routeDir,
665
+ tsConfigFilePath
666
+ );
667
+ serverModuleCache.set(jayHtmlPath, cached);
668
+ }
669
+ const htmlChunks = [];
670
+ const asyncPromises = [];
671
+ const ctx = {
672
+ write: (chunk) => {
673
+ htmlChunks.push(chunk);
674
+ },
675
+ onAsync: (promise, id, templates) => {
676
+ const asyncPromise = promise.then(
677
+ (val) => {
678
+ if (templates.resolved) {
679
+ return templates.resolved(val);
680
+ }
681
+ return "";
682
+ },
683
+ (err) => {
684
+ if (templates.rejected) {
685
+ return templates.rejected(err);
686
+ }
687
+ return "";
688
+ }
689
+ );
690
+ asyncPromises.push(asyncPromise);
691
+ }
692
+ };
693
+ cached.renderToStream(viewState, ctx);
694
+ const asyncResults = await Promise.all(asyncPromises);
695
+ const ssrHtml = htmlChunks.join("");
696
+ const asyncScripts = asyncResults.filter((r) => r !== "").join("");
697
+ const hydrationScript = generateHydrationScript(
698
+ viewState,
699
+ carryForward,
700
+ parts,
701
+ jayHtmlImportPath,
702
+ trackByMap,
703
+ clientInitData2,
704
+ projectInit,
705
+ pluginInits,
706
+ options
707
+ );
708
+ const headLinksHtml = cached.headLinks.map((link) => {
709
+ const attrs = Object.entries(link.attributes).map(([k, v]) => ` ${k}="${v}"`).join("");
710
+ return ` <link rel="${link.rel}" href="${link.href}"${attrs} />`;
711
+ }).join("\n");
712
+ const inlineCss = cached.css ? ` <style>
713
+ ${cached.css}
714
+ </style>` : "";
715
+ const headExtras = [headLinksHtml, inlineCss].filter((_) => _).join("\n");
716
+ return `<!doctype html>
717
+ <html lang="en">
718
+ <head>
719
+ <meta charset="UTF-8" />
720
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
721
+ <title>Vite + TS</title>
722
+ ${headExtras ? headExtras + "\n" : ""} </head>
723
+ <body>
724
+ <div id="target">${ssrHtml}</div>${asyncScripts}
725
+ ${hydrationScript}
726
+ </body>
727
+ </html>`;
728
+ }
729
+ function rebaseRelativeImports(code, fromDir, toDir) {
730
+ return code.replace(/from "(\.\.\/[^"]+)"/g, (_match, relPath) => {
731
+ const absolutePath = path__default.resolve(fromDir, relPath);
732
+ let newRelPath = path__default.relative(toDir, absolutePath);
733
+ if (!newRelPath.startsWith(".")) {
734
+ newRelPath = "./" + newRelPath;
735
+ }
736
+ return `from "${newRelPath}"`;
737
+ });
738
+ }
739
+ async function compileAndLoadServerElement(vite, jayHtmlContent, jayHtmlFilename, jayHtmlDir, buildFolder, projectRoot, routeDir, tsConfigFilePath) {
740
+ const jayFile = await parseJayFile(
741
+ jayHtmlContent,
742
+ jayHtmlFilename,
743
+ jayHtmlDir,
744
+ { relativePath: tsConfigFilePath },
745
+ JAY_IMPORT_RESOLVER,
746
+ projectRoot
747
+ );
748
+ const parsedJayFile = checkValidationErrors(jayFile);
749
+ const pageName = jayHtmlFilename.replace(".jay-html", "");
750
+ const debugCoordinatePreprocessPath = path__default.join(
751
+ buildFolder,
752
+ "debug",
753
+ routeDir,
754
+ `${pageName}.coordinate-preprocess.jay-html`
755
+ );
756
+ const serverElementOptions = {
757
+ debugCoordinatePreprocessPath
758
+ };
759
+ const serverElementCode = checkValidationErrors(
760
+ generateServerElementFile(parsedJayFile, serverElementOptions)
761
+ );
762
+ const serverElementDir = path__default.join(buildFolder, "pre-rendered", routeDir);
763
+ await fs$2.mkdir(serverElementDir, { recursive: true });
764
+ const adjustedCode = rebaseRelativeImports(serverElementCode, jayHtmlDir, serverElementDir);
765
+ const serverElementFilename = jayHtmlFilename.replace(".jay-html", ".server-element.ts");
766
+ const serverElementPath = path__default.join(serverElementDir, serverElementFilename);
767
+ await fs$2.writeFile(serverElementPath, adjustedCode, "utf-8");
768
+ const serverModule = await vite.ssrLoadModule(serverElementPath);
769
+ return {
770
+ renderToStream: serverModule.renderToStream,
771
+ headLinks: parsedJayFile.headLinks,
772
+ css: parsedJayFile.css
773
+ };
774
+ }
775
+ function generateHydrationScript(defaultViewState, fastCarryForward, parts, jayHtmlPath, trackByMap = {}, clientInitData2 = {}, projectInit, pluginInits = [], options = {}) {
776
+ const {
777
+ partImports,
778
+ compositeParts,
779
+ pluginClientInitImports,
780
+ projectInitImport,
781
+ clientInitExecution,
782
+ automationImport,
783
+ slowViewStateDecl
784
+ } = buildScriptFragments(parts, clientInitData2, projectInit, pluginInits, options);
785
+ const automationWrap = buildAutomationWrap(options, "hydrate");
786
+ const hydrateImportPath = `${jayHtmlPath}${JAY_QUERY_HYDRATE}`;
787
+ return `<script type="module">
788
+ import {hydrateCompositeJayComponent} from "@jay-framework/stack-client-runtime";
789
+ ${automationImport}
790
+ ${pluginClientInitImports}
791
+ ${projectInitImport}
792
+ import { hydrate } from '${hydrateImportPath}';
793
+ ${partImports}${slowViewStateDecl}
794
+ const viewState = ${JSON.stringify(defaultViewState)};
795
+ const fastCarryForward = ${JSON.stringify(fastCarryForward)};
796
+ const trackByMap = ${JSON.stringify(trackByMap)};
797
+
798
+ const target = document.getElementById('target');
799
+ const rootElement = target.firstElementChild;
800
+ const pageComp = hydrateCompositeJayComponent(hydrate, viewState, fastCarryForward, ${compositeParts}, trackByMap, rootElement);
801
+ ${clientInitExecution}
802
+ const instance = pageComp({/* placeholder for page props */});
803
+ ${automationWrap}
804
+ <\/script>`;
805
+ }
271
806
  const require$3 = createRequire(import.meta.url);
272
807
  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);
808
+ const exists = await fs$2.access(route.compPath, fs$2.constants.F_OK).then(() => true).catch(() => false);
274
809
  const parts = [];
275
810
  if (exists) {
276
811
  const pageComponent = (await vite.ssrLoadModule(route.compPath)).page;
@@ -281,7 +816,7 @@ async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfi
281
816
  });
282
817
  }
283
818
  const jayHtmlFilePath = options?.preRenderedPath ?? route.jayHtmlPath;
284
- const jayHtmlSource = (await fs$1.readFile(jayHtmlFilePath)).toString();
819
+ const jayHtmlSource = options?.preRenderedContent ?? (await fs$2.readFile(jayHtmlFilePath)).toString();
285
820
  const fileName = path__default.basename(route.jayHtmlPath);
286
821
  const dirName = path__default.dirname(route.jayHtmlPath);
287
822
  const jayHtmlWithValidations = await parseJayFile(
@@ -338,13 +873,16 @@ async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfi
338
873
  contract: hi.contract,
339
874
  contractPath: hi.contractPath
340
875
  }));
876
+ const discoveryResult = headlessInstanceComponents.length > 0 ? discoverHeadlessInstances(jayHtmlSource) : { instances: [], forEachInstances: [], preRenderedJayHtml: jayHtmlSource };
341
877
  return {
342
878
  parts,
343
879
  serverTrackByMap: jayHtml.serverTrackByMap,
344
880
  clientTrackByMap: jayHtml.clientTrackByMap,
345
881
  usedPackages,
346
882
  headlessContracts,
347
- headlessInstanceComponents
883
+ headlessInstanceComponents,
884
+ discoveredInstances: discoveryResult.instances,
885
+ forEachInstances: discoveryResult.forEachInstances
348
886
  };
349
887
  });
350
888
  }
@@ -362,8 +900,14 @@ async function slowRenderInstances(discovered, headlessInstanceComponents) {
362
900
  if (!comp || !comp.compDefinition.slowlyRender) {
363
901
  continue;
364
902
  }
903
+ const contractProps = comp.contract?.props ?? [];
904
+ const normalizedProps = {};
905
+ for (const [key, value] of Object.entries(instance.props)) {
906
+ const match = contractProps.find((p) => p.name.toLowerCase() === key.toLowerCase());
907
+ normalizedProps[match ? match.name : key] = value;
908
+ }
365
909
  const services = resolveServices(comp.compDefinition.services);
366
- const slowResult = await comp.compDefinition.slowlyRender(instance.props, ...services);
910
+ const slowResult = await comp.compDefinition.slowlyRender(normalizedProps, ...services);
367
911
  if (slowResult.kind === "PhaseOutput") {
368
912
  const coordKey = instance.coordinate.join("/");
369
913
  resolvedData.push({
@@ -375,7 +919,7 @@ async function slowRenderInstances(discovered, headlessInstanceComponents) {
375
919
  carryForwards[coordKey] = slowResult.carryForward;
376
920
  discoveredForFast.push({
377
921
  contractName: instance.contractName,
378
- props: instance.props,
922
+ props: normalizedProps,
379
923
  coordinate: instance.coordinate
380
924
  });
381
925
  }
@@ -551,200 +1095,35 @@ class ActionRegistry {
551
1095
  const parts = [];
552
1096
  if (maxAge !== void 0) {
553
1097
  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;
1098
+ }
1099
+ if (staleWhileRevalidate !== void 0) {
1100
+ parts.push(`stale-while-revalidate=${staleWhileRevalidate}`);
1101
+ }
1102
+ return parts.length > 0 ? parts.join(", ") : void 0;
605
1103
  }
606
1104
  }
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;
1105
+ const actionRegistry = new ActionRegistry();
1106
+ function registerAction(action) {
1107
+ actionRegistry.register(action);
620
1108
  }
621
- function isImportedType(aType) {
622
- return aType.kind === 4;
1109
+ function getRegisteredAction(actionName) {
1110
+ return actionRegistry.get(actionName);
623
1111
  }
624
- function isObjectType(aType) {
625
- return aType.kind === 8;
1112
+ function hasAction(actionName) {
1113
+ return actionRegistry.has(actionName);
626
1114
  }
627
- function isArrayType(aType) {
628
- return aType.kind === 9;
1115
+ function getRegisteredActionNames() {
1116
+ return actionRegistry.getNames();
629
1117
  }
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;
1118
+ function clearActionRegistry() {
1119
+ actionRegistry.clear();
674
1120
  }
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 };
1121
+ async function executeAction(actionName, input) {
1122
+ return actionRegistry.execute(actionName, input);
729
1123
  }
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
- }
1124
+ function getActionCacheHeaders(actionName) {
1125
+ return actionRegistry.getCacheHeaders(actionName);
741
1126
  }
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
1127
  function parseActionMetadata(yamlContent, fileName) {
749
1128
  try {
750
1129
  const parsed = parseAction(yamlContent, fileName);
@@ -1352,16 +1731,8 @@ function preparePluginClientInits(plugins) {
1352
1731
  };
1353
1732
  });
1354
1733
  }
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
- }
1734
+ const CACHE_TAG_START = '<script type="application/jay-cache">';
1735
+ const CACHE_TAG_END = "<\/script>";
1365
1736
  function hashParams(params) {
1366
1737
  const sortedParams = Object.keys(params).sort().reduce(
1367
1738
  (acc, key) => {
@@ -1375,81 +1746,126 @@ function hashParams(params) {
1375
1746
  return "";
1376
1747
  return "_" + crypto.createHash("md5").update(json).digest("hex").substring(0, 8);
1377
1748
  }
1749
+ function embedCacheMetadata(jayHtmlContent, slowViewState, carryForward, sourcePath) {
1750
+ const metadata = JSON.stringify({ slowViewState, carryForward, sourcePath });
1751
+ const cacheTag = `${CACHE_TAG_START}${metadata}${CACHE_TAG_END}`;
1752
+ const headMatch = jayHtmlContent.match(/<head[^>]*>/i);
1753
+ if (headMatch) {
1754
+ const insertPos = headMatch.index + headMatch[0].length;
1755
+ return jayHtmlContent.substring(0, insertPos) + "\n" + cacheTag + jayHtmlContent.substring(insertPos);
1756
+ }
1757
+ return `${cacheTag}
1758
+ ${jayHtmlContent}`;
1759
+ }
1760
+ function extractCacheMetadata(fileContent) {
1761
+ const startIdx = fileContent.indexOf(CACHE_TAG_START);
1762
+ if (startIdx === -1)
1763
+ return void 0;
1764
+ const jsonStart = startIdx + CACHE_TAG_START.length;
1765
+ const endIdx = fileContent.indexOf(CACHE_TAG_END, jsonStart);
1766
+ if (endIdx === -1)
1767
+ return void 0;
1768
+ const jsonStr = fileContent.substring(jsonStart, endIdx);
1769
+ const metadata = JSON.parse(jsonStr);
1770
+ const tagEnd = endIdx + CACHE_TAG_END.length;
1771
+ const afterTag = fileContent[tagEnd] === "\n" ? tagEnd + 1 : tagEnd;
1772
+ const content = fileContent.substring(0, startIdx) + fileContent.substring(afterTag);
1773
+ return {
1774
+ content,
1775
+ slowViewState: metadata.slowViewState,
1776
+ carryForward: metadata.carryForward,
1777
+ sourcePath: metadata.sourcePath
1778
+ };
1779
+ }
1378
1780
  class SlowRenderCache {
1379
1781
  /**
1380
1782
  * @param cacheDir - Directory where pre-rendered jay-html files are stored
1381
1783
  * @param pagesRoot - Root directory of the pages (for relative path calculation)
1382
1784
  */
1383
1785
  constructor(cacheDir, pagesRoot) {
1384
- __publicField(this, "cache", /* @__PURE__ */ new Map());
1385
- __publicField(this, "pathToKeys", /* @__PURE__ */ new Map());
1786
+ /** Maps source jay-html path set of pre-rendered file paths (for invalidation) */
1787
+ __publicField(this, "pathToFiles", /* @__PURE__ */ new Map());
1386
1788
  __publicField(this, "cacheDir");
1387
1789
  __publicField(this, "pagesRoot");
1388
1790
  this.cacheDir = cacheDir;
1389
1791
  this.pagesRoot = pagesRoot;
1390
1792
  }
1391
1793
  /**
1392
- * Get a cached pre-rendered jay-html entry
1794
+ * Get a cached pre-rendered jay-html entry by reading from disk.
1795
+ * Returns undefined if the cache file doesn't exist or has no metadata tag.
1393
1796
  */
1394
- get(jayHtmlPath, params) {
1395
- const key = makeCacheKey(jayHtmlPath, params);
1396
- return this.cache.get(key);
1797
+ async get(jayHtmlPath, params) {
1798
+ const preRenderedPath = this.computeCachePath(jayHtmlPath, params);
1799
+ let fileContent;
1800
+ try {
1801
+ fileContent = await fs$2.readFile(preRenderedPath, "utf-8");
1802
+ } catch {
1803
+ return void 0;
1804
+ }
1805
+ const extracted = extractCacheMetadata(fileContent);
1806
+ if (!extracted)
1807
+ return void 0;
1808
+ this.trackFile(jayHtmlPath, preRenderedPath);
1809
+ return {
1810
+ preRenderedPath,
1811
+ preRenderedContent: extracted.content,
1812
+ slowViewState: extracted.slowViewState,
1813
+ carryForward: extracted.carryForward,
1814
+ sourcePath: extracted.sourcePath
1815
+ };
1397
1816
  }
1398
1817
  /**
1399
- * Store a pre-rendered jay-html entry in the cache.
1400
- * Writes the pre-rendered content to disk and stores metadata in memory.
1818
+ * Store a pre-rendered jay-html entry.
1819
+ * Embeds metadata as a <script> tag and writes to disk.
1820
+ * Returns the full cache entry with stripped content.
1401
1821
  */
1402
1822
  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 = {
1823
+ const preRenderedPath = this.computeCachePath(jayHtmlPath, params);
1824
+ const fileContent = embedCacheMetadata(
1825
+ preRenderedJayHtml,
1826
+ slowViewState,
1827
+ carryForward,
1828
+ jayHtmlPath
1829
+ );
1830
+ await fs$2.mkdir(path__default.dirname(preRenderedPath), { recursive: true });
1831
+ await fs$2.writeFile(preRenderedPath, fileContent, "utf-8");
1832
+ this.trackFile(jayHtmlPath, preRenderedPath);
1833
+ return {
1417
1834
  preRenderedPath,
1835
+ preRenderedContent: preRenderedJayHtml,
1418
1836
  slowViewState,
1419
1837
  carryForward,
1420
- createdAt: Date.now(),
1421
1838
  sourcePath: jayHtmlPath
1422
1839
  };
1423
- this.cache.set(key, entry);
1424
- return preRenderedPath;
1425
1840
  }
1426
1841
  /**
1427
1842
  * Check if a pre-rendered entry exists for the given path and params
1428
1843
  */
1429
- has(jayHtmlPath, params) {
1430
- const key = makeCacheKey(jayHtmlPath, params);
1431
- return this.cache.has(key);
1844
+ async has(jayHtmlPath, params) {
1845
+ const preRenderedPath = this.computeCachePath(jayHtmlPath, params);
1846
+ try {
1847
+ await fs$2.access(preRenderedPath);
1848
+ return true;
1849
+ } catch {
1850
+ return false;
1851
+ }
1432
1852
  }
1433
1853
  /**
1434
1854
  * 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.
1855
+ * Deletes cached files from disk.
1437
1856
  */
1438
1857
  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
- }
1858
+ const files = this.pathToFiles.get(jayHtmlPath);
1859
+ if (files) {
1860
+ for (const filePath of files) {
1861
+ try {
1862
+ await fs$2.unlink(filePath);
1863
+ } catch {
1448
1864
  }
1449
- this.cache.delete(key);
1450
1865
  }
1451
- this.pathToKeys.delete(jayHtmlPath);
1866
+ this.pathToFiles.delete(jayHtmlPath);
1452
1867
  }
1868
+ await this.scanAndDeleteCacheFiles(jayHtmlPath);
1453
1869
  }
1454
1870
  /**
1455
1871
  * Invalidate all entries that depend on a changed file.
@@ -1477,26 +1893,67 @@ class SlowRenderCache {
1477
1893
  * Clear all cached entries and delete cached files from disk
1478
1894
  */
1479
1895
  async clear() {
1480
- for (const entry of this.cache.values()) {
1481
- try {
1482
- await fs$1.unlink(entry.preRenderedPath);
1483
- } catch {
1896
+ for (const files of this.pathToFiles.values()) {
1897
+ for (const filePath of files) {
1898
+ try {
1899
+ await fs$2.unlink(filePath);
1900
+ } catch {
1901
+ }
1484
1902
  }
1485
1903
  }
1486
- this.cache.clear();
1487
- this.pathToKeys.clear();
1904
+ this.pathToFiles.clear();
1905
+ try {
1906
+ await fs$2.rm(this.cacheDir, { recursive: true, force: true });
1907
+ } catch {
1908
+ }
1488
1909
  }
1489
1910
  /**
1490
- * Get the number of cached entries
1911
+ * Get all cached jay-html paths (for debugging/monitoring)
1491
1912
  */
1492
- get size() {
1493
- return this.cache.size;
1913
+ getCachedPaths() {
1914
+ return Array.from(this.pathToFiles.keys());
1494
1915
  }
1495
1916
  /**
1496
- * Get all cached jay-html paths (for debugging/monitoring)
1917
+ * Compute the cache file path for a given jay-html path and params.
1497
1918
  */
1498
- getCachedPaths() {
1499
- return Array.from(this.pathToKeys.keys());
1919
+ computeCachePath(jayHtmlPath, params) {
1920
+ const relativePath = path__default.relative(this.pagesRoot, jayHtmlPath);
1921
+ const dir = path__default.dirname(relativePath);
1922
+ const basename = path__default.basename(relativePath, ".jay-html");
1923
+ const paramsHash = hashParams(params);
1924
+ const cacheFileName = `${basename}${paramsHash}.jay-html`;
1925
+ return path__default.join(this.cacheDir, dir, cacheFileName);
1926
+ }
1927
+ /**
1928
+ * Track a pre-rendered file path for invalidation.
1929
+ */
1930
+ trackFile(jayHtmlPath, preRenderedPath) {
1931
+ if (!this.pathToFiles.has(jayHtmlPath)) {
1932
+ this.pathToFiles.set(jayHtmlPath, /* @__PURE__ */ new Set());
1933
+ }
1934
+ this.pathToFiles.get(jayHtmlPath).add(preRenderedPath);
1935
+ }
1936
+ /**
1937
+ * Scan the cache directory for files matching a route and delete them.
1938
+ * Handles the startup case where pathToFiles is not populated from a previous session.
1939
+ */
1940
+ async scanAndDeleteCacheFiles(jayHtmlPath) {
1941
+ const relativePath = path__default.relative(this.pagesRoot, jayHtmlPath);
1942
+ const dir = path__default.dirname(relativePath);
1943
+ const basename = path__default.basename(relativePath, ".jay-html");
1944
+ const cacheSubDir = path__default.join(this.cacheDir, dir);
1945
+ try {
1946
+ const files = await fs$2.readdir(cacheSubDir);
1947
+ for (const file of files) {
1948
+ if (file.startsWith(basename) && file.endsWith(".jay-html")) {
1949
+ try {
1950
+ await fs$2.unlink(path__default.join(cacheSubDir, file));
1951
+ } catch {
1952
+ }
1953
+ }
1954
+ }
1955
+ } catch {
1956
+ }
1500
1957
  }
1501
1958
  }
1502
1959
  const require2 = createRequire(import.meta.url);
@@ -1640,13 +2097,12 @@ function getJayStackVersion() {
1640
2097
  async function materializeContracts(options, services = /* @__PURE__ */ new Map()) {
1641
2098
  const {
1642
2099
  projectRoot,
1643
- outputDir = path.join(projectRoot, "build", "materialized-contracts"),
2100
+ outputDir = path.join(projectRoot, "agent-kit", "materialized-contracts"),
1644
2101
  dynamicOnly = false,
1645
2102
  pluginFilter,
1646
2103
  verbose = false,
1647
2104
  viteServer
1648
2105
  } = options;
1649
- const contracts = [];
1650
2106
  const pluginsIndexMap = /* @__PURE__ */ new Map();
1651
2107
  let staticCount = 0;
1652
2108
  let dynamicCount = 0;
@@ -1670,6 +2126,14 @@ async function materializeContracts(options, services = /* @__PURE__ */ new Map(
1670
2126
  📦 Processing plugin: ${plugin.name}`);
1671
2127
  }
1672
2128
  const { manifest } = plugin;
2129
+ const pluginRelPath = path.relative(projectRoot, plugin.pluginPath);
2130
+ if (!pluginsIndexMap.has(plugin.name)) {
2131
+ pluginsIndexMap.set(plugin.name, {
2132
+ path: "./" + pluginRelPath.replace(/\\/g, "/"),
2133
+ contracts: [],
2134
+ actions: []
2135
+ });
2136
+ }
1673
2137
  if (!dynamicOnly && manifest.contracts) {
1674
2138
  for (const contract of manifest.contracts) {
1675
2139
  const contractPath = resolveStaticContractPath(
@@ -1678,27 +2142,12 @@ async function materializeContracts(options, services = /* @__PURE__ */ new Map(
1678
2142
  projectRoot
1679
2143
  );
1680
2144
  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
2145
  pluginsIndexMap.get(plugin.name).contracts.push({
1698
2146
  name: contract.name,
1699
2147
  type: "static",
1700
2148
  path: "./" + relativePath
1701
2149
  });
2150
+ staticCount++;
1702
2151
  if (verbose) {
1703
2152
  getLogger().info(` 📄 Static: ${contract.name}`);
1704
2153
  }
@@ -1728,28 +2177,14 @@ async function materializeContracts(options, services = /* @__PURE__ */ new Map(
1728
2177
  const filePath = path.join(pluginOutputDir, fileName);
1729
2178
  fs.writeFileSync(filePath, generated.yaml, "utf-8");
1730
2179
  const relativePath = path.relative(projectRoot, filePath);
1731
- const dynEntry = {
1732
- plugin: plugin.name,
2180
+ const contractEntry = {
1733
2181
  name: fullName,
1734
2182
  type: "dynamic",
1735
2183
  path: "./" + relativePath,
1736
- metadata: generated.metadata
2184
+ ...generated.metadata && { metadata: generated.metadata }
1737
2185
  };
1738
- contracts.push(dynEntry);
2186
+ pluginsIndexMap.get(plugin.name).contracts.push(contractEntry);
1739
2187
  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
2188
  if (verbose) {
1754
2189
  getLogger().info(` ⚡ Materialized: ${fullName}`);
1755
2190
  }
@@ -1778,14 +2213,6 @@ async function materializeContracts(options, services = /* @__PURE__ */ new Map(
1778
2213
  const metadata = loadActionMetadata(metadataFilePath);
1779
2214
  if (!metadata)
1780
2215
  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
2216
  const actionRelPath = path.relative(projectRoot, metadataFilePath);
1790
2217
  pluginsIndexMap.get(plugin.name).actions.push({
1791
2218
  name: metadata.name,
@@ -1798,17 +2225,8 @@ async function materializeContracts(options, services = /* @__PURE__ */ new Map(
1798
2225
  }
1799
2226
  }
1800
2227
  }
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
2228
  const pluginsIndex = {
1810
- materialized_at: index.materialized_at,
1811
- jay_stack_version: index.jay_stack_version,
2229
+ jay_stack_version: getJayStackVersion(),
1812
2230
  plugins: Array.from(pluginsIndexMap.entries()).map(([name, data]) => ({
1813
2231
  name,
1814
2232
  path: data.path,
@@ -1816,15 +2234,16 @@ async function materializeContracts(options, services = /* @__PURE__ */ new Map(
1816
2234
  ...data.actions.length > 0 && { actions: data.actions }
1817
2235
  }))
1818
2236
  };
1819
- const pluginsIndexPath = path.join(outputDir, "plugins-index.yaml");
2237
+ fs.mkdirSync(outputDir, { recursive: true });
2238
+ const agentKitDir = path.dirname(outputDir);
2239
+ const pluginsIndexPath = path.join(agentKitDir, "plugins-index.yaml");
1820
2240
  fs.writeFileSync(pluginsIndexPath, YAML.stringify(pluginsIndex), "utf-8");
1821
2241
  if (verbose) {
1822
2242
  getLogger().info(`
1823
- Contracts index written to: ${indexPath}`);
1824
- getLogger().info(`✅ Plugins index written to: ${pluginsIndexPath}`);
2243
+ Plugins index written to: ${pluginsIndexPath}`);
1825
2244
  }
1826
2245
  return {
1827
- index,
2246
+ pluginsIndex,
1828
2247
  staticCount,
1829
2248
  dynamicCount,
1830
2249
  outputDir
@@ -1832,7 +2251,7 @@ async function materializeContracts(options, services = /* @__PURE__ */ new Map(
1832
2251
  }
1833
2252
  async function listContracts(options) {
1834
2253
  const { projectRoot, dynamicOnly = false, pluginFilter } = options;
1835
- const contracts = [];
2254
+ const pluginsMap = /* @__PURE__ */ new Map();
1836
2255
  const plugins = await scanPlugins({
1837
2256
  projectRoot,
1838
2257
  includeDevDeps: true
@@ -1841,6 +2260,13 @@ async function listContracts(options) {
1841
2260
  if (pluginFilter && plugin.name !== pluginFilter && pluginKey !== pluginFilter)
1842
2261
  continue;
1843
2262
  const { manifest } = plugin;
2263
+ const pluginRelPath = path.relative(projectRoot, plugin.pluginPath);
2264
+ if (!pluginsMap.has(plugin.name)) {
2265
+ pluginsMap.set(plugin.name, {
2266
+ path: "./" + pluginRelPath.replace(/\\/g, "/"),
2267
+ contracts: []
2268
+ });
2269
+ }
1844
2270
  if (!dynamicOnly && manifest.contracts) {
1845
2271
  for (const contract of manifest.contracts) {
1846
2272
  const contractPath = resolveStaticContractPath(
@@ -1849,8 +2275,7 @@ async function listContracts(options) {
1849
2275
  projectRoot
1850
2276
  );
1851
2277
  const relativePath = path.relative(projectRoot, contractPath);
1852
- contracts.push({
1853
- plugin: plugin.name,
2278
+ pluginsMap.get(plugin.name).contracts.push({
1854
2279
  name: contract.name,
1855
2280
  type: "static",
1856
2281
  path: "./" + relativePath
@@ -1860,8 +2285,7 @@ async function listContracts(options) {
1860
2285
  if (manifest.dynamic_contracts) {
1861
2286
  const dynamicConfigs = Array.isArray(manifest.dynamic_contracts) ? manifest.dynamic_contracts : [manifest.dynamic_contracts];
1862
2287
  for (const config of dynamicConfigs) {
1863
- contracts.push({
1864
- plugin: plugin.name,
2288
+ pluginsMap.get(plugin.name).contracts.push({
1865
2289
  name: `${config.prefix}/*`,
1866
2290
  type: "dynamic",
1867
2291
  path: "(run materialization to generate)"
@@ -1870,9 +2294,12 @@ async function listContracts(options) {
1870
2294
  }
1871
2295
  }
1872
2296
  return {
1873
- materialized_at: (/* @__PURE__ */ new Date()).toISOString(),
1874
2297
  jay_stack_version: getJayStackVersion(),
1875
- contracts
2298
+ plugins: Array.from(pluginsMap.entries()).map(([name, data]) => ({
2299
+ name,
2300
+ path: data.path,
2301
+ contracts: data.contracts
2302
+ }))
1876
2303
  };
1877
2304
  }
1878
2305
  async function discoverPluginsWithSetup(options) {
@@ -2007,9 +2434,12 @@ export {
2007
2434
  DevSlowlyChangingPhase,
2008
2435
  SlowRenderCache,
2009
2436
  actionRegistry,
2437
+ buildAutomationWrap,
2438
+ buildScriptFragments,
2010
2439
  clearActionRegistry,
2011
2440
  clearClientInitData,
2012
2441
  clearLifecycleCallbacks,
2442
+ clearServerElementCache,
2013
2443
  clearServiceRegistry,
2014
2444
  discoverAllPluginActions,
2015
2445
  discoverAndRegisterActions,
@@ -2022,6 +2452,7 @@ export {
2022
2452
  executePluginServerInits,
2023
2453
  executePluginSetup,
2024
2454
  generateClientScript,
2455
+ generateSSRPageHtml,
2025
2456
  getActionCacheHeaders,
2026
2457
  getClientInitData,
2027
2458
  getClientInitDataForKey,
@@ -2031,6 +2462,7 @@ export {
2031
2462
  getServiceRegistry,
2032
2463
  hasAction,
2033
2464
  hasService,
2465
+ invalidateServerElementCache,
2034
2466
  listContracts,
2035
2467
  loadActionMetadata,
2036
2468
  loadPageParts,