@jay-framework/stack-server-runtime 0.10.0 → 0.12.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 +440 -4
  2. package/dist/index.js +859 -89
  3. package/package.json +13 -11
package/dist/index.js CHANGED
@@ -17,6 +17,8 @@ import "js-beautify";
17
17
  import fs$2 from "fs";
18
18
  import path$1 from "path";
19
19
  import YAML from "yaml";
20
+ import { getLogger } from "@jay-framework/logger";
21
+ import crypto from "node:crypto";
20
22
  const serviceRegistry = /* @__PURE__ */ new Map();
21
23
  function registerService(marker, service) {
22
24
  serviceRegistry.set(marker, service);
@@ -39,9 +41,13 @@ function hasService(marker) {
39
41
  function clearServiceRegistry() {
40
42
  serviceRegistry.clear();
41
43
  }
44
+ function getServiceRegistry() {
45
+ return serviceRegistry;
46
+ }
42
47
  function resolveServices(serviceMarkers) {
43
48
  return serviceMarkers.map((marker) => getService(marker));
44
49
  }
50
+ globalThis.__JAY_SERVICE_RESOLVER__ = resolveServices;
45
51
  const initCallbacks = [];
46
52
  const shutdownCallbacks = [];
47
53
  function onInit(callback) {
@@ -93,10 +99,17 @@ class DevSlowlyChangingPhase {
93
99
  }
94
100
  async runSlowlyForPage(pageParams, pageProps, parts) {
95
101
  for (const part of parts) {
96
- const { compDefinition } = part;
102
+ const { compDefinition, contractInfo } = part;
97
103
  if (compDefinition.loadParams) {
98
104
  const services = resolveServices(compDefinition.services);
99
- const compParams = compDefinition.loadParams(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);
100
113
  if (!await findMatchingParams(pageParams, compParams))
101
114
  return notFound();
102
115
  }
@@ -104,11 +117,19 @@ class DevSlowlyChangingPhase {
104
117
  let slowlyViewState = {};
105
118
  let carryForward = {};
106
119
  for (const part of parts) {
107
- const { compDefinition, key } = part;
120
+ const { compDefinition, key, contractInfo } = part;
108
121
  if (compDefinition.slowlyRender) {
109
122
  const services = resolveServices(compDefinition.services);
123
+ const partProps = {
124
+ ...pageProps,
125
+ ...pageParams,
126
+ ...contractInfo && {
127
+ contractName: contractInfo.contractName,
128
+ metadata: contractInfo.metadata
129
+ }
130
+ };
110
131
  const slowlyRenderedPart = await compDefinition.slowlyRender(
111
- { ...pageProps, ...pageParams },
132
+ partProps,
112
133
  ...services
113
134
  );
114
135
  if (slowlyRenderedPart.kind === "PhaseOutput") {
@@ -135,12 +156,20 @@ async function renderFastChangingData(pageParams, pageProps, carryForward, parts
135
156
  let fastViewState = {};
136
157
  let fastCarryForward = {};
137
158
  for (const part of parts) {
138
- const { compDefinition, key } = part;
159
+ const { compDefinition, key, contractInfo } = part;
139
160
  if (compDefinition.fastRender) {
140
161
  const partSlowlyCarryForward = key ? carryForward[key] : carryForward;
141
162
  const services = resolveServices(compDefinition.services);
163
+ const partProps = {
164
+ ...pageProps,
165
+ ...pageParams,
166
+ ...contractInfo && {
167
+ contractName: contractInfo.contractName,
168
+ metadata: contractInfo.metadata
169
+ }
170
+ };
142
171
  const fastRenderedPart = await compDefinition.fastRender(
143
- { ...pageProps, ...pageParams },
172
+ partProps,
144
173
  partSlowlyCarryForward,
145
174
  ...services
146
175
  );
@@ -158,7 +187,9 @@ async function renderFastChangingData(pageParams, pageProps, carryForward, parts
158
187
  }
159
188
  return Promise.resolve(phaseOutput(fastViewState, fastCarryForward));
160
189
  }
161
- function generateClientScript(defaultViewState, fastCarryForward, parts, jayHtmlPath, trackByMap = {}, clientInitData2 = {}, projectInit, pluginInits = []) {
190
+ function generateClientScript(defaultViewState, fastCarryForward, parts, jayHtmlPath, trackByMap = {}, clientInitData2 = {}, projectInit, pluginInits = [], options = {}) {
191
+ const { enableAutomation = true, slowViewState } = options;
192
+ const hasSlowViewState = slowViewState && Object.keys(slowViewState).length > 0;
162
193
  const imports = parts.length > 0 ? parts.map((part) => part.clientImport).join("\n") + "\n" : "";
163
194
  const compositeParts = parts.length > 0 ? `[
164
195
  ${parts.map((part) => " " + part.clientPart).join(",\n")}
@@ -187,6 +218,27 @@ ${parts.map((part) => " " + part.clientPart).join(",\n")}
187
218
  // Project client initialization
188
219
  ${projectInitCall}
189
220
  ` : "";
221
+ const automationImport = enableAutomation ? hasSlowViewState ? `import { wrapWithAutomation, AUTOMATION_CONTEXT } from "@jay-framework/runtime-automation";
222
+ import { registerGlobalContext } from "@jay-framework/runtime";
223
+ import { deepMergeViewStates } from "@jay-framework/view-state-merge";` : `import { wrapWithAutomation, AUTOMATION_CONTEXT } from "@jay-framework/runtime-automation";
224
+ import { registerGlobalContext } from "@jay-framework/runtime";` : "";
225
+ const slowViewStateDecl = enableAutomation && hasSlowViewState ? `const slowViewState = ${JSON.stringify(slowViewState)};` : "";
226
+ const automationWrap = enableAutomation ? hasSlowViewState ? `
227
+ // Wrap with automation for dev tooling
228
+ // Deep merge slow+fast ViewState so automation can see full page state
229
+ const fullViewState = deepMergeViewStates(slowViewState, {...viewState, ...fastCarryForward}, trackByMap);
230
+ const wrapped = wrapWithAutomation(instance, { initialViewState: fullViewState, trackByMap });
231
+ registerGlobalContext(AUTOMATION_CONTEXT, wrapped.automation);
232
+ window.__jay = window.__jay || {};
233
+ window.__jay.automation = wrapped.automation;
234
+ target.appendChild(wrapped.element.dom);` : `
235
+ // Wrap with automation for dev tooling
236
+ const wrapped = wrapWithAutomation(instance);
237
+ registerGlobalContext(AUTOMATION_CONTEXT, wrapped.automation);
238
+ window.__jay = window.__jay || {};
239
+ window.__jay.automation = wrapped.automation;
240
+ target.appendChild(wrapped.element.dom);` : `
241
+ target.appendChild(instance.element.dom);`;
190
242
  return `<!doctype html>
191
243
  <html lang="en">
192
244
  <head>
@@ -198,10 +250,11 @@ ${parts.map((part) => " " + part.clientPart).join(",\n")}
198
250
  <div id="target"></div>
199
251
  <script type="module">
200
252
  import {makeCompositeJayComponent} from "@jay-framework/stack-client-runtime";
253
+ ${automationImport}
201
254
  ${pluginClientInitImports}
202
255
  ${projectInitImport}
203
256
  import { render } from '${jayHtmlPath}';
204
- ${imports}
257
+ ${imports}${slowViewStateDecl}
205
258
  const viewState = ${JSON.stringify(defaultViewState)};
206
259
  const fastCarryForward = ${JSON.stringify(fastCarryForward)};
207
260
  const trackByMap = ${JSON.stringify(trackByMap)};
@@ -209,26 +262,26 @@ ${clientInitExecution}
209
262
  const target = document.getElementById('target');
210
263
  const pageComp = makeCompositeJayComponent(render, viewState, fastCarryForward, ${compositeParts}, trackByMap)
211
264
 
212
- const instance = pageComp({...viewState, ...fastCarryForward})
213
- target.appendChild(instance.element.dom);
265
+ const instance = pageComp({/* placeholder for page props */})
266
+ ${automationWrap}
214
267
  <\/script>
215
268
  </body>
216
269
  </html>`;
217
270
  }
218
- const require$2 = createRequire(import.meta.url);
219
- async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfig) {
271
+ const require$3 = createRequire(import.meta.url);
272
+ async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfig, options) {
220
273
  const exists = await fs$1.access(route.compPath, fs$1.constants.F_OK).then(() => true).catch(() => false);
221
274
  const parts = [];
222
275
  if (exists) {
223
276
  const pageComponent = (await vite.ssrLoadModule(route.compPath)).page;
224
277
  parts.push({
225
278
  compDefinition: pageComponent,
226
- // Client import uses client-only code (server code stripped)
227
279
  clientImport: `import {page} from '${route.compPath}'`,
228
- clientPart: `{comp: page.comp, contextMarkers: []}`
280
+ clientPart: `{comp: page.comp, contextMarkers: page.contexts || []}`
229
281
  });
230
282
  }
231
- const jayHtmlSource = (await fs$1.readFile(route.jayHtmlPath)).toString();
283
+ const jayHtmlFilePath = options?.preRenderedPath ?? route.jayHtmlPath;
284
+ const jayHtmlSource = (await fs$1.readFile(jayHtmlFilePath)).toString();
232
285
  const fileName = path__default.basename(route.jayHtmlPath);
233
286
  const dirName = path__default.dirname(route.jayHtmlPath);
234
287
  const jayHtmlWithValidations = await parseJayFile(
@@ -243,11 +296,12 @@ async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfi
243
296
  );
244
297
  return jayHtmlWithValidations.mapAsync(async (jayHtml) => {
245
298
  const usedPackages = /* @__PURE__ */ new Set();
299
+ const headlessInstanceComponents = [];
246
300
  for await (const headlessImport of jayHtml.headlessImports) {
247
301
  const module = headlessImport.codeLink.module;
248
302
  const name = headlessImport.codeLink.names[0].name;
249
303
  const isLocalModule = module[0] === "." || module[0] === "/";
250
- const modulePath = isLocalModule ? path__default.resolve(dirName, module) : require$2.resolve(module, { paths: require$2.resolve.paths(dirName) });
304
+ const modulePath = isLocalModule ? path__default.resolve(dirName, module) : require$3.resolve(module, { paths: require$3.resolve.paths(dirName) });
251
305
  const compDefinition = (await vite.ssrLoadModule(modulePath))[name];
252
306
  const moduleImport = isLocalModule ? path__default.resolve(dirName, module) : module;
253
307
  const isNpmPackage = !isLocalModule;
@@ -256,23 +310,85 @@ async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfi
256
310
  const packageName = module.startsWith("@") ? module.split("/").slice(0, 2).join("/") : module.split("/")[0];
257
311
  usedPackages.add(packageName);
258
312
  }
259
- const key = headlessImport.key;
260
- const part = {
261
- key,
262
- compDefinition,
263
- clientImport: `import {${name}} from '${clientModuleImport}'`,
264
- clientPart: `{comp: ${name}.comp, contextMarkers: [], key: '${headlessImport.key}'}`
265
- };
266
- parts.push(part);
313
+ if (headlessImport.key) {
314
+ const key = headlessImport.key;
315
+ const part = {
316
+ key,
317
+ compDefinition,
318
+ clientImport: `import {${name}} from '${clientModuleImport}'`,
319
+ clientPart: `{comp: ${name}.comp, contextMarkers: ${name}.contexts || [], key: '${key}'}`,
320
+ // Include contract info for dynamic contract components
321
+ contractInfo: headlessImport.contract ? {
322
+ contractName: headlessImport.contract.name,
323
+ metadata: headlessImport.metadata
324
+ } : void 0
325
+ };
326
+ parts.push(part);
327
+ }
328
+ if (!headlessImport.key && headlessImport.contract) {
329
+ headlessInstanceComponents.push({
330
+ contractName: headlessImport.contractName,
331
+ compDefinition,
332
+ contract: headlessImport.contract
333
+ });
334
+ }
267
335
  }
336
+ const headlessContracts = jayHtml.headlessImports.filter((hi) => hi.contract !== void 0 && hi.key !== void 0).map((hi) => ({
337
+ key: hi.key,
338
+ contract: hi.contract,
339
+ contractPath: hi.contractPath
340
+ }));
268
341
  return {
269
342
  parts,
270
343
  serverTrackByMap: jayHtml.serverTrackByMap,
271
344
  clientTrackByMap: jayHtml.clientTrackByMap,
272
- usedPackages
345
+ usedPackages,
346
+ headlessContracts,
347
+ headlessInstanceComponents
273
348
  };
274
349
  });
275
350
  }
351
+ async function slowRenderInstances(discovered, headlessInstanceComponents) {
352
+ const componentByContractName = /* @__PURE__ */ new Map();
353
+ for (const comp of headlessInstanceComponents) {
354
+ componentByContractName.set(comp.contractName, comp);
355
+ }
356
+ const resolvedData = [];
357
+ const slowViewStates = {};
358
+ const discoveredForFast = [];
359
+ const carryForwards = {};
360
+ for (const instance of discovered) {
361
+ const comp = componentByContractName.get(instance.contractName);
362
+ if (!comp || !comp.compDefinition.slowlyRender) {
363
+ continue;
364
+ }
365
+ const services = resolveServices(comp.compDefinition.services);
366
+ const slowResult = await comp.compDefinition.slowlyRender(instance.props, ...services);
367
+ if (slowResult.kind === "PhaseOutput") {
368
+ const coordKey = instance.coordinate.join("/");
369
+ resolvedData.push({
370
+ coordinate: instance.coordinate,
371
+ contract: comp.contract,
372
+ slowViewState: slowResult.rendered
373
+ });
374
+ slowViewStates[coordKey] = slowResult.rendered;
375
+ carryForwards[coordKey] = slowResult.carryForward;
376
+ discoveredForFast.push({
377
+ contractName: instance.contractName,
378
+ props: instance.props,
379
+ coordinate: instance.coordinate
380
+ });
381
+ }
382
+ }
383
+ if (discoveredForFast.length === 0) {
384
+ return void 0;
385
+ }
386
+ return {
387
+ resolvedData,
388
+ slowViewStates,
389
+ instancePhaseData: { discovered: discoveredForFast, carryForwards }
390
+ };
391
+ }
276
392
  class ActionRegistry {
277
393
  constructor() {
278
394
  __publicField(this, "actions", /* @__PURE__ */ new Map());
@@ -516,7 +632,7 @@ new Proxy(e, {
516
632
  return t[r];
517
633
  }
518
634
  });
519
- const require$1 = createRequire$1(import.meta.url);
635
+ const require$2 = createRequire$1(import.meta.url);
520
636
  async function discoverAndRegisterActions(options) {
521
637
  const {
522
638
  projectRoot,
@@ -533,13 +649,13 @@ async function discoverAndRegisterActions(options) {
533
649
  const actionsPath = path.resolve(projectRoot, actionsDir);
534
650
  if (!fs.existsSync(actionsPath)) {
535
651
  if (verbose) {
536
- console.log(`[Actions] No actions directory found at ${actionsPath}`);
652
+ getLogger().info(`[Actions] No actions directory found at ${actionsPath}`);
537
653
  }
538
654
  return result;
539
655
  }
540
656
  const actionFiles = await findActionFiles(actionsPath);
541
657
  if (verbose) {
542
- console.log(`[Actions] Found ${actionFiles.length} action file(s)`);
658
+ getLogger().info(`[Actions] Found ${actionFiles.length} action file(s)`);
543
659
  }
544
660
  for (const filePath of actionFiles) {
545
661
  result.scannedFiles.push(filePath);
@@ -556,12 +672,14 @@ async function discoverAndRegisterActions(options) {
556
672
  result.actionNames.push(exportValue.actionName);
557
673
  result.actionCount++;
558
674
  if (verbose) {
559
- console.log(`[Actions] Registered: ${exportValue.actionName}`);
675
+ getLogger().info(
676
+ `[Actions] Registered: ${exportValue.actionName}`
677
+ );
560
678
  }
561
679
  }
562
680
  }
563
681
  } catch (error) {
564
- console.error(`[Actions] Failed to import ${filePath}:`, error);
682
+ getLogger().error(`[Actions] Failed to import ${filePath}: ${error}`);
565
683
  }
566
684
  }
567
685
  return result;
@@ -628,9 +746,8 @@ async function discoverNpmPluginActions(projectRoot, registry, verbose, viteServ
628
746
  continue;
629
747
  }
630
748
  if (verbose) {
631
- console.log(
632
- `[Actions] NPM plugin "${packageName}" declares actions:`,
633
- pluginConfig.actions
749
+ getLogger().info(
750
+ `[Actions] NPM plugin "${packageName}" declares actions: ${JSON.stringify(pluginConfig.actions)}`
634
751
  );
635
752
  }
636
753
  const actions = await registerNpmPluginActions(
@@ -646,13 +763,13 @@ async function discoverNpmPluginActions(projectRoot, registry, verbose, viteServ
646
763
  }
647
764
  }
648
765
  } catch (error) {
649
- console.error("[Actions] Failed to read project package.json:", error);
766
+ getLogger().error(`[Actions] Failed to read project package.json: ${error}`);
650
767
  }
651
768
  return allActions;
652
769
  }
653
770
  function tryResolvePluginYaml(packageName, projectRoot) {
654
771
  try {
655
- return require$1.resolve(`${packageName}/plugin.yaml`, {
772
+ return require$2.resolve(`${packageName}/plugin.yaml`, {
656
773
  paths: [projectRoot]
657
774
  });
658
775
  } catch {
@@ -674,18 +791,18 @@ async function registerNpmPluginActions(packageName, pluginConfig, registry, ver
674
791
  registry.register(actionExport);
675
792
  registeredActions.push(actionExport.actionName);
676
793
  if (verbose) {
677
- console.log(
794
+ getLogger().info(
678
795
  `[Actions] Registered NPM plugin action: ${actionExport.actionName}`
679
796
  );
680
797
  }
681
798
  } else {
682
- console.warn(
799
+ getLogger().warn(
683
800
  `[Actions] NPM plugin "${packageName}" declares action "${actionName}" but it's not exported or not a JayAction`
684
801
  );
685
802
  }
686
803
  }
687
804
  } catch (importError) {
688
- console.error(`[Actions] Failed to import NPM plugin "${packageName}":`, importError);
805
+ getLogger().error(`[Actions] Failed to import NPM plugin "${packageName}": ${importError}`);
689
806
  }
690
807
  return registeredActions;
691
808
  }
@@ -700,7 +817,9 @@ async function discoverPluginActions(pluginPath, projectRoot, registry = actionR
700
817
  const registeredActions = [];
701
818
  const pluginName = pluginConfig.name || path.basename(pluginPath);
702
819
  if (verbose) {
703
- console.log(`[Actions] Plugin "${pluginName}" declares actions:`, pluginConfig.actions);
820
+ getLogger().info(
821
+ `[Actions] Plugin "${pluginName}" declares actions: ${JSON.stringify(pluginConfig.actions)}`
822
+ );
704
823
  }
705
824
  let modulePath = pluginConfig.module ? path.join(pluginPath, pluginConfig.module) : path.join(pluginPath, "index.ts");
706
825
  if (!fs.existsSync(modulePath)) {
@@ -711,7 +830,7 @@ async function discoverPluginActions(pluginPath, projectRoot, registry = actionR
711
830
  } else if (fs.existsSync(jsPath)) {
712
831
  modulePath = jsPath;
713
832
  } else {
714
- console.warn(`[Actions] Plugin "${pluginName}" module not found at ${modulePath}`);
833
+ getLogger().warn(`[Actions] Plugin "${pluginName}" module not found at ${modulePath}`);
715
834
  return [];
716
835
  }
717
836
  }
@@ -728,25 +847,33 @@ async function discoverPluginActions(pluginPath, projectRoot, registry = actionR
728
847
  registry.register(actionExport);
729
848
  registeredActions.push(actionExport.actionName);
730
849
  if (verbose) {
731
- console.log(
850
+ getLogger().info(
732
851
  `[Actions] Registered plugin action: ${actionExport.actionName}`
733
852
  );
734
853
  }
735
854
  } else {
736
- console.warn(
855
+ getLogger().warn(
737
856
  `[Actions] Plugin "${pluginName}" declares action "${actionName}" but it's not exported or not a JayAction`
738
857
  );
739
858
  }
740
859
  }
741
860
  } catch (importError) {
742
- console.error(`[Actions] Failed to import plugin module at ${modulePath}:`, importError);
861
+ getLogger().error(
862
+ `[Actions] Failed to import plugin module at ${modulePath}: ${importError}`
863
+ );
743
864
  }
744
865
  return registeredActions;
745
866
  }
746
- const require2 = createRequire$1(import.meta.url);
747
- async function discoverPluginsWithInit(options) {
748
- const { projectRoot, verbose = false } = options;
749
- const plugins = [];
867
+ const require$1 = createRequire$1(import.meta.url);
868
+ async function scanPlugins(options) {
869
+ const {
870
+ projectRoot,
871
+ verbose = false,
872
+ includeDevDeps = false,
873
+ discoverTransitive = false
874
+ } = options;
875
+ const plugins = /* @__PURE__ */ new Map();
876
+ const visitedPackages = /* @__PURE__ */ new Set();
750
877
  const localPluginsPath = path.join(projectRoot, "src/plugins");
751
878
  if (fs.existsSync(localPluginsPath)) {
752
879
  try {
@@ -758,42 +885,45 @@ async function discoverPluginsWithInit(options) {
758
885
  const manifest = loadPluginManifest(pluginPath);
759
886
  if (!manifest)
760
887
  continue;
761
- const initConfig = resolvePluginInit(pluginPath, manifest.init, true);
762
- if (!initConfig)
763
- continue;
764
888
  const dependencies = await getPackageDependencies(pluginPath);
765
- plugins.push({
766
- name: manifest.name || entry.name,
889
+ const pluginName = manifest.name || entry.name;
890
+ plugins.set(entry.name, {
891
+ name: pluginName,
767
892
  pluginPath,
768
893
  packageName: pluginPath,
769
- // For local, use path
894
+ // For local, use path as identifier
770
895
  isLocal: true,
771
- initModule: initConfig.module,
772
- initExport: initConfig.export,
896
+ manifest,
773
897
  dependencies
774
898
  });
899
+ visitedPackages.add(pluginPath);
775
900
  if (verbose) {
776
- console.log(
777
- `[PluginInit] Found local plugin with init: ${manifest.name || entry.name}`
778
- );
901
+ getLogger().info(`[PluginScanner] Found local plugin: ${pluginName}`);
779
902
  }
780
903
  }
781
904
  } catch (error) {
782
- console.warn(`[PluginInit] Failed to scan local plugins: ${error}`);
905
+ if (verbose) {
906
+ getLogger().warn(`[PluginScanner] Failed to scan local plugins: ${error}`);
907
+ }
783
908
  }
784
909
  }
785
910
  const projectPackageJsonPath = path.join(projectRoot, "package.json");
786
911
  if (fs.existsSync(projectPackageJsonPath)) {
787
912
  try {
788
913
  const projectPackageJson = JSON.parse(fs.readFileSync(projectPackageJsonPath, "utf-8"));
789
- const allDeps = {
914
+ const deps = {
790
915
  ...projectPackageJson.dependencies,
791
- ...projectPackageJson.devDependencies
916
+ ...includeDevDeps ? projectPackageJson.devDependencies : {}
792
917
  };
793
- for (const depName of Object.keys(allDeps)) {
918
+ const packagesToCheck = Object.keys(deps);
919
+ while (packagesToCheck.length > 0) {
920
+ const depName = packagesToCheck.shift();
921
+ if (visitedPackages.has(depName))
922
+ continue;
923
+ visitedPackages.add(depName);
794
924
  let pluginYamlPath;
795
925
  try {
796
- pluginYamlPath = require2.resolve(`${depName}/plugin.yaml`, {
926
+ pluginYamlPath = require$1.resolve(`${depName}/plugin.yaml`, {
797
927
  paths: [projectRoot]
798
928
  });
799
929
  } catch {
@@ -803,25 +933,76 @@ async function discoverPluginsWithInit(options) {
803
933
  const manifest = loadPluginManifest(pluginPath);
804
934
  if (!manifest)
805
935
  continue;
806
- const initConfig = resolvePluginInit(pluginPath, manifest.init, false);
807
- if (!initConfig)
808
- continue;
809
936
  const dependencies = await getPackageDependencies(pluginPath);
810
- plugins.push({
937
+ plugins.set(depName, {
811
938
  name: manifest.name || depName,
812
939
  pluginPath,
813
940
  packageName: depName,
814
941
  isLocal: false,
815
- initModule: initConfig.module,
816
- initExport: initConfig.export,
942
+ manifest,
817
943
  dependencies
818
944
  });
819
945
  if (verbose) {
820
- console.log(`[PluginInit] Found NPM plugin with init: ${depName}`);
946
+ getLogger().info(`[PluginScanner] Found NPM plugin: ${depName}`);
947
+ }
948
+ if (discoverTransitive) {
949
+ for (const transitiveDep of dependencies) {
950
+ if (!visitedPackages.has(transitiveDep)) {
951
+ packagesToCheck.push(transitiveDep);
952
+ }
953
+ }
821
954
  }
822
955
  }
823
956
  } catch (error) {
824
- console.warn(`[PluginInit] Failed to scan NPM plugins: ${error}`);
957
+ if (verbose) {
958
+ getLogger().warn(`[PluginScanner] Failed to scan NPM plugins: ${error}`);
959
+ }
960
+ }
961
+ }
962
+ return plugins;
963
+ }
964
+ async function getPackageDependencies(pluginPath) {
965
+ const packageJsonPath = path.join(pluginPath, "package.json");
966
+ if (!fs.existsSync(packageJsonPath)) {
967
+ return [];
968
+ }
969
+ try {
970
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
971
+ return Object.keys(packageJson.dependencies || {});
972
+ } catch {
973
+ return [];
974
+ }
975
+ }
976
+ async function discoverPluginsWithInit(options) {
977
+ const { projectRoot, verbose = false } = options;
978
+ const scannedPlugins = await scanPlugins({
979
+ projectRoot,
980
+ verbose,
981
+ includeDevDeps: false,
982
+ // Only runtime dependencies
983
+ discoverTransitive: true
984
+ // Need transitive for dependency ordering
985
+ });
986
+ const plugins = [];
987
+ for (const [key, scanned] of scannedPlugins) {
988
+ const initConfig = resolvePluginInit(
989
+ scanned.pluginPath,
990
+ scanned.manifest.init,
991
+ scanned.isLocal
992
+ );
993
+ if (!initConfig)
994
+ continue;
995
+ plugins.push({
996
+ name: scanned.name,
997
+ pluginPath: scanned.pluginPath,
998
+ packageName: scanned.packageName,
999
+ isLocal: scanned.isLocal,
1000
+ initModule: initConfig.module,
1001
+ initExport: initConfig.export,
1002
+ dependencies: scanned.dependencies
1003
+ });
1004
+ if (verbose) {
1005
+ getLogger().info(`[PluginInit] Found plugin with init: ${scanned.name}`);
825
1006
  }
826
1007
  }
827
1008
  return plugins;
@@ -861,18 +1042,6 @@ function resolvePluginInit(pluginPath, initConfig, isLocal) {
861
1042
  return null;
862
1043
  }
863
1044
  }
864
- async function getPackageDependencies(pluginPath) {
865
- const packageJsonPath = path.join(pluginPath, "package.json");
866
- if (!fs.existsSync(packageJsonPath)) {
867
- return [];
868
- }
869
- try {
870
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
871
- return Object.keys(packageJson.dependencies || {});
872
- } catch {
873
- return [];
874
- }
875
- }
876
1045
  function sortPluginsByDependencies(plugins) {
877
1046
  const pluginNames = new Set(plugins.map((p) => p.packageName));
878
1047
  const sorted = [];
@@ -882,7 +1051,7 @@ function sortPluginsByDependencies(plugins) {
882
1051
  if (visited.has(plugin.packageName))
883
1052
  return;
884
1053
  if (visiting.has(plugin.packageName)) {
885
- console.warn(`[PluginInit] Circular dependency detected for ${plugin.name}`);
1054
+ getLogger().warn(`[PluginInit] Circular dependency detected for ${plugin.name}`);
886
1055
  return;
887
1056
  }
888
1057
  visiting.add(plugin.packageName);
@@ -922,14 +1091,14 @@ async function executePluginServerInits(plugins, viteServer, verbose = false) {
922
1091
  }
923
1092
  const jayInit = pluginModule[plugin.initExport];
924
1093
  if (!jayInit || jayInit.__brand !== "JayInit") {
925
- console.warn(
1094
+ getLogger().warn(
926
1095
  `[PluginInit] Plugin "${plugin.name}" init module doesn't export a valid JayInit at "${plugin.initExport}"`
927
1096
  );
928
1097
  continue;
929
1098
  }
930
1099
  if (typeof jayInit._serverInit === "function") {
931
1100
  if (verbose) {
932
- console.log(`[DevServer] Running server init: ${plugin.name}`);
1101
+ getLogger().info(`[DevServer] Running server init: ${plugin.name}`);
933
1102
  }
934
1103
  const clientData = await jayInit._serverInit();
935
1104
  if (clientData !== void 0 && clientData !== null) {
@@ -937,9 +1106,8 @@ async function executePluginServerInits(plugins, viteServer, verbose = false) {
937
1106
  }
938
1107
  }
939
1108
  } catch (error) {
940
- console.error(
941
- `[PluginInit] Failed to execute server init for "${plugin.name}":`,
942
- error
1109
+ getLogger().error(
1110
+ `[PluginInit] Failed to execute server init for "${plugin.name}": ${error}`
943
1111
  );
944
1112
  }
945
1113
  }
@@ -961,9 +1129,602 @@ function preparePluginClientInits(plugins) {
961
1129
  };
962
1130
  });
963
1131
  }
1132
+ function makeCacheKey(jayHtmlPath, params) {
1133
+ const sortedParams = Object.keys(params).sort().reduce(
1134
+ (acc, key) => {
1135
+ acc[key] = params[key];
1136
+ return acc;
1137
+ },
1138
+ {}
1139
+ );
1140
+ return `${jayHtmlPath}:${JSON.stringify(sortedParams)}`;
1141
+ }
1142
+ function hashParams(params) {
1143
+ const sortedParams = Object.keys(params).sort().reduce(
1144
+ (acc, key) => {
1145
+ acc[key] = params[key];
1146
+ return acc;
1147
+ },
1148
+ {}
1149
+ );
1150
+ const json = JSON.stringify(sortedParams);
1151
+ if (json === "{}")
1152
+ return "";
1153
+ return "_" + crypto.createHash("md5").update(json).digest("hex").substring(0, 8);
1154
+ }
1155
+ class SlowRenderCache {
1156
+ /**
1157
+ * @param cacheDir - Directory where pre-rendered jay-html files are stored
1158
+ * @param pagesRoot - Root directory of the pages (for relative path calculation)
1159
+ */
1160
+ constructor(cacheDir, pagesRoot) {
1161
+ __publicField(this, "cache", /* @__PURE__ */ new Map());
1162
+ __publicField(this, "pathToKeys", /* @__PURE__ */ new Map());
1163
+ __publicField(this, "cacheDir");
1164
+ __publicField(this, "pagesRoot");
1165
+ this.cacheDir = cacheDir;
1166
+ this.pagesRoot = pagesRoot;
1167
+ }
1168
+ /**
1169
+ * Get a cached pre-rendered jay-html entry
1170
+ */
1171
+ get(jayHtmlPath, params) {
1172
+ const key = makeCacheKey(jayHtmlPath, params);
1173
+ return this.cache.get(key);
1174
+ }
1175
+ /**
1176
+ * Store a pre-rendered jay-html entry in the cache.
1177
+ * Writes the pre-rendered content to disk and stores metadata in memory.
1178
+ */
1179
+ async set(jayHtmlPath, params, preRenderedJayHtml, slowViewState, carryForward) {
1180
+ const key = makeCacheKey(jayHtmlPath, params);
1181
+ const relativePath = path__default.relative(this.pagesRoot, jayHtmlPath);
1182
+ const dir = path__default.dirname(relativePath);
1183
+ const basename = path__default.basename(relativePath, ".jay-html");
1184
+ const paramsHash = hashParams(params);
1185
+ const cacheFileName = `${basename}${paramsHash}.jay-html`;
1186
+ const preRenderedPath = path__default.join(this.cacheDir, dir, cacheFileName);
1187
+ await fs$1.mkdir(path__default.dirname(preRenderedPath), { recursive: true });
1188
+ await fs$1.writeFile(preRenderedPath, preRenderedJayHtml, "utf-8");
1189
+ if (!this.pathToKeys.has(jayHtmlPath)) {
1190
+ this.pathToKeys.set(jayHtmlPath, /* @__PURE__ */ new Set());
1191
+ }
1192
+ this.pathToKeys.get(jayHtmlPath).add(key);
1193
+ const entry = {
1194
+ preRenderedPath,
1195
+ slowViewState,
1196
+ carryForward,
1197
+ createdAt: Date.now(),
1198
+ sourcePath: jayHtmlPath
1199
+ };
1200
+ this.cache.set(key, entry);
1201
+ return preRenderedPath;
1202
+ }
1203
+ /**
1204
+ * Check if a pre-rendered entry exists for the given path and params
1205
+ */
1206
+ has(jayHtmlPath, params) {
1207
+ const key = makeCacheKey(jayHtmlPath, params);
1208
+ return this.cache.has(key);
1209
+ }
1210
+ /**
1211
+ * Invalidate all cached entries for a given jay-html source path.
1212
+ * This is called when the source file changes.
1213
+ * Also deletes the cached files from disk.
1214
+ */
1215
+ async invalidate(jayHtmlPath) {
1216
+ const keys = this.pathToKeys.get(jayHtmlPath);
1217
+ if (keys) {
1218
+ for (const key of keys) {
1219
+ const entry = this.cache.get(key);
1220
+ if (entry) {
1221
+ try {
1222
+ await fs$1.unlink(entry.preRenderedPath);
1223
+ } catch {
1224
+ }
1225
+ }
1226
+ this.cache.delete(key);
1227
+ }
1228
+ this.pathToKeys.delete(jayHtmlPath);
1229
+ }
1230
+ }
1231
+ /**
1232
+ * Invalidate all entries that depend on a changed file.
1233
+ * The changedPath could be:
1234
+ * - A jay-html file itself
1235
+ * - A component file (page.ts)
1236
+ * - Any other dependency
1237
+ *
1238
+ * @param changedPath - Absolute path to the changed file
1239
+ * @param resolveDependencies - Optional function to resolve which jay-html files depend on the changed file
1240
+ */
1241
+ async invalidateByDependency(changedPath, resolveDependencies) {
1242
+ if (changedPath.endsWith(".jay-html")) {
1243
+ await this.invalidate(changedPath);
1244
+ return;
1245
+ }
1246
+ if (resolveDependencies) {
1247
+ const dependentPaths = resolveDependencies(changedPath);
1248
+ for (const depPath of dependentPaths) {
1249
+ await this.invalidate(depPath);
1250
+ }
1251
+ }
1252
+ }
1253
+ /**
1254
+ * Clear all cached entries and delete cached files from disk
1255
+ */
1256
+ async clear() {
1257
+ for (const entry of this.cache.values()) {
1258
+ try {
1259
+ await fs$1.unlink(entry.preRenderedPath);
1260
+ } catch {
1261
+ }
1262
+ }
1263
+ this.cache.clear();
1264
+ this.pathToKeys.clear();
1265
+ }
1266
+ /**
1267
+ * Get the number of cached entries
1268
+ */
1269
+ get size() {
1270
+ return this.cache.size;
1271
+ }
1272
+ /**
1273
+ * Get all cached jay-html paths (for debugging/monitoring)
1274
+ */
1275
+ getCachedPaths() {
1276
+ return Array.from(this.pathToKeys.keys());
1277
+ }
1278
+ }
1279
+ const require2 = createRequire(import.meta.url);
1280
+ async function executeDynamicGenerator(plugin, config, projectRoot, services, verbose, viteServer) {
1281
+ const { pluginPath, name: pluginName, isLocal, packageName } = plugin;
1282
+ if (!config.generator) {
1283
+ throw new Error(
1284
+ `Plugin "${pluginName}" has dynamic_contracts entry but no generator specified`
1285
+ );
1286
+ }
1287
+ const isFilePath = config.generator.startsWith("./") || config.generator.startsWith("/") || config.generator.includes(".ts") || config.generator.includes(".js");
1288
+ let generator;
1289
+ if (isFilePath) {
1290
+ let generatorPath;
1291
+ if (!isLocal) {
1292
+ try {
1293
+ generatorPath = require2.resolve(`${packageName}/${config.generator}`, {
1294
+ paths: [projectRoot]
1295
+ });
1296
+ } catch {
1297
+ generatorPath = path.join(pluginPath, config.generator);
1298
+ }
1299
+ } else {
1300
+ generatorPath = path.join(pluginPath, config.generator);
1301
+ }
1302
+ if (!fs.existsSync(generatorPath)) {
1303
+ const withTs = generatorPath + ".ts";
1304
+ const withJs = generatorPath + ".js";
1305
+ if (fs.existsSync(withTs)) {
1306
+ generatorPath = withTs;
1307
+ } else if (fs.existsSync(withJs)) {
1308
+ generatorPath = withJs;
1309
+ }
1310
+ }
1311
+ if (!fs.existsSync(generatorPath)) {
1312
+ throw new Error(
1313
+ `Generator file not found for plugin "${pluginName}": ${config.generator}`
1314
+ );
1315
+ }
1316
+ if (verbose) {
1317
+ getLogger().info(` Loading generator from file: ${generatorPath}`);
1318
+ }
1319
+ let generatorModule;
1320
+ if (viteServer) {
1321
+ generatorModule = await viteServer.ssrLoadModule(generatorPath);
1322
+ } else {
1323
+ generatorModule = await import(generatorPath);
1324
+ }
1325
+ generator = generatorModule.generator || generatorModule.default;
1326
+ } else {
1327
+ if (verbose) {
1328
+ getLogger().info(
1329
+ ` Loading generator export: ${config.generator} from ${packageName}`
1330
+ );
1331
+ }
1332
+ let pluginModule;
1333
+ if (viteServer) {
1334
+ pluginModule = await viteServer.ssrLoadModule(packageName);
1335
+ } else {
1336
+ pluginModule = await import(packageName);
1337
+ }
1338
+ generator = pluginModule[config.generator];
1339
+ if (!generator) {
1340
+ throw new Error(
1341
+ `Generator "${config.generator}" not exported from plugin "${pluginName}". Ensure it's exported from the package's index.ts`
1342
+ );
1343
+ }
1344
+ }
1345
+ if (!generator || typeof generator.generate !== "function") {
1346
+ throw new Error(
1347
+ `Generator "${config.generator}" for plugin "${pluginName}" must have a 'generate' function. Use makeContractGenerator() to create valid generators.`
1348
+ );
1349
+ }
1350
+ const resolvedServices = [];
1351
+ for (const marker of generator.services) {
1352
+ const service = services.get(marker);
1353
+ if (!service) {
1354
+ const markerName = marker.description ?? "unknown";
1355
+ throw new Error(
1356
+ `Service "${markerName}" required by ${pluginName} generator not found. Ensure it's registered in init.ts`
1357
+ );
1358
+ }
1359
+ resolvedServices.push(service);
1360
+ }
1361
+ if (verbose) {
1362
+ getLogger().info(` Executing generator...`);
1363
+ }
1364
+ const result = await generator.generate(...resolvedServices);
1365
+ return result;
1366
+ }
1367
+ function resolveStaticContractPath(plugin, contractSpec, projectRoot) {
1368
+ const { pluginPath, isLocal, packageName } = plugin;
1369
+ if (!isLocal) {
1370
+ try {
1371
+ return require2.resolve(`${packageName}/${contractSpec}`, {
1372
+ paths: [projectRoot]
1373
+ });
1374
+ } catch {
1375
+ const possiblePaths = [
1376
+ path.join(pluginPath, "dist", contractSpec),
1377
+ path.join(pluginPath, "lib", contractSpec),
1378
+ path.join(pluginPath, contractSpec)
1379
+ ];
1380
+ const found = possiblePaths.find((p) => fs.existsSync(p));
1381
+ return found || possiblePaths[0];
1382
+ }
1383
+ } else {
1384
+ return path.join(pluginPath, contractSpec);
1385
+ }
1386
+ }
1387
+ function toKebabCase(str) {
1388
+ return str.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "");
1389
+ }
1390
+ function getJayStackVersion() {
1391
+ try {
1392
+ const packageJsonPath = path.join(__dirname, "..", "package.json");
1393
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
1394
+ return packageJson.version || "0.0.0";
1395
+ } catch {
1396
+ return "0.0.0";
1397
+ }
1398
+ }
1399
+ async function materializeContracts(options, services = /* @__PURE__ */ new Map()) {
1400
+ const {
1401
+ projectRoot,
1402
+ outputDir = path.join(projectRoot, "build", "materialized-contracts"),
1403
+ dynamicOnly = false,
1404
+ pluginFilter,
1405
+ verbose = false,
1406
+ viteServer
1407
+ } = options;
1408
+ const contracts = [];
1409
+ const pluginsIndexMap = /* @__PURE__ */ new Map();
1410
+ let staticCount = 0;
1411
+ let dynamicCount = 0;
1412
+ if (verbose) {
1413
+ getLogger().info("Scanning for plugins...");
1414
+ }
1415
+ const plugins = await scanPlugins({
1416
+ projectRoot,
1417
+ verbose,
1418
+ includeDevDeps: true
1419
+ // Include dev deps for contract discovery
1420
+ });
1421
+ if (verbose) {
1422
+ getLogger().info(`Found ${plugins.size} plugin(s)`);
1423
+ }
1424
+ for (const [pluginKey, plugin] of plugins) {
1425
+ if (pluginFilter && plugin.name !== pluginFilter && pluginKey !== pluginFilter)
1426
+ continue;
1427
+ if (verbose) {
1428
+ getLogger().info(`
1429
+ 📦 Processing plugin: ${plugin.name}`);
1430
+ }
1431
+ const { manifest } = plugin;
1432
+ if (!dynamicOnly && manifest.contracts) {
1433
+ for (const contract of manifest.contracts) {
1434
+ const contractPath = resolveStaticContractPath(
1435
+ plugin,
1436
+ contract.contract,
1437
+ projectRoot
1438
+ );
1439
+ const relativePath = path.relative(projectRoot, contractPath);
1440
+ const entry = {
1441
+ plugin: plugin.name,
1442
+ name: contract.name,
1443
+ type: "static",
1444
+ path: "./" + relativePath
1445
+ };
1446
+ contracts.push(entry);
1447
+ staticCount++;
1448
+ const pluginRelPath = path.relative(projectRoot, plugin.pluginPath);
1449
+ if (!pluginsIndexMap.has(plugin.name)) {
1450
+ pluginsIndexMap.set(plugin.name, {
1451
+ path: "./" + pluginRelPath.replace(/\\/g, "/"),
1452
+ contracts: []
1453
+ });
1454
+ }
1455
+ pluginsIndexMap.get(plugin.name).contracts.push({
1456
+ name: contract.name,
1457
+ type: "static",
1458
+ path: "./" + relativePath
1459
+ });
1460
+ if (verbose) {
1461
+ getLogger().info(` 📄 Static: ${contract.name}`);
1462
+ }
1463
+ }
1464
+ }
1465
+ if (manifest.dynamic_contracts) {
1466
+ const dynamicConfigs = Array.isArray(manifest.dynamic_contracts) ? manifest.dynamic_contracts : [manifest.dynamic_contracts];
1467
+ const pluginOutputDir = path.join(outputDir, plugin.name.replace(/[@/]/g, "_"));
1468
+ fs.mkdirSync(pluginOutputDir, { recursive: true });
1469
+ for (const config of dynamicConfigs) {
1470
+ if (verbose) {
1471
+ getLogger().info(` ⚡ Dynamic contracts (prefix: ${config.prefix})`);
1472
+ }
1473
+ try {
1474
+ const generatedContracts = await executeDynamicGenerator(
1475
+ plugin,
1476
+ config,
1477
+ projectRoot,
1478
+ services,
1479
+ verbose,
1480
+ viteServer
1481
+ );
1482
+ const prefix = config.prefix;
1483
+ for (const generated of generatedContracts) {
1484
+ const fullName = `${prefix}/${toKebabCase(generated.name)}`;
1485
+ const fileName = `${prefix}-${toKebabCase(generated.name)}.jay-contract`;
1486
+ const filePath = path.join(pluginOutputDir, fileName);
1487
+ fs.writeFileSync(filePath, generated.yaml, "utf-8");
1488
+ const relativePath = path.relative(projectRoot, filePath);
1489
+ const dynEntry = {
1490
+ plugin: plugin.name,
1491
+ name: fullName,
1492
+ type: "dynamic",
1493
+ path: "./" + relativePath,
1494
+ metadata: generated.metadata
1495
+ };
1496
+ contracts.push(dynEntry);
1497
+ dynamicCount++;
1498
+ const pluginRelPath = path.relative(projectRoot, plugin.pluginPath);
1499
+ if (!pluginsIndexMap.has(plugin.name)) {
1500
+ pluginsIndexMap.set(plugin.name, {
1501
+ path: "./" + pluginRelPath.replace(/\\/g, "/"),
1502
+ contracts: []
1503
+ });
1504
+ }
1505
+ pluginsIndexMap.get(plugin.name).contracts.push({
1506
+ name: fullName,
1507
+ type: "dynamic",
1508
+ path: "./" + relativePath
1509
+ });
1510
+ if (verbose) {
1511
+ getLogger().info(` ⚡ Materialized: ${fullName}`);
1512
+ }
1513
+ }
1514
+ } catch (error) {
1515
+ getLogger().error(
1516
+ ` ❌ Failed to materialize dynamic contracts for ${plugin.name} (${config.prefix}): ${error}`
1517
+ );
1518
+ }
1519
+ }
1520
+ }
1521
+ }
1522
+ const index = {
1523
+ materialized_at: (/* @__PURE__ */ new Date()).toISOString(),
1524
+ jay_stack_version: getJayStackVersion(),
1525
+ contracts
1526
+ };
1527
+ fs.mkdirSync(outputDir, { recursive: true });
1528
+ const indexPath = path.join(outputDir, "contracts-index.yaml");
1529
+ fs.writeFileSync(indexPath, YAML.stringify(index), "utf-8");
1530
+ const pluginsIndex = {
1531
+ materialized_at: index.materialized_at,
1532
+ jay_stack_version: index.jay_stack_version,
1533
+ plugins: Array.from(pluginsIndexMap.entries()).map(([name, data]) => ({
1534
+ name,
1535
+ path: data.path,
1536
+ contracts: data.contracts
1537
+ }))
1538
+ };
1539
+ const pluginsIndexPath = path.join(outputDir, "plugins-index.yaml");
1540
+ fs.writeFileSync(pluginsIndexPath, YAML.stringify(pluginsIndex), "utf-8");
1541
+ if (verbose) {
1542
+ getLogger().info(`
1543
+ ✅ Contracts index written to: ${indexPath}`);
1544
+ getLogger().info(`✅ Plugins index written to: ${pluginsIndexPath}`);
1545
+ }
1546
+ return {
1547
+ index,
1548
+ staticCount,
1549
+ dynamicCount,
1550
+ outputDir
1551
+ };
1552
+ }
1553
+ async function listContracts(options) {
1554
+ const { projectRoot, dynamicOnly = false, pluginFilter } = options;
1555
+ const contracts = [];
1556
+ const plugins = await scanPlugins({
1557
+ projectRoot,
1558
+ includeDevDeps: true
1559
+ });
1560
+ for (const [pluginKey, plugin] of plugins) {
1561
+ if (pluginFilter && plugin.name !== pluginFilter && pluginKey !== pluginFilter)
1562
+ continue;
1563
+ const { manifest } = plugin;
1564
+ if (!dynamicOnly && manifest.contracts) {
1565
+ for (const contract of manifest.contracts) {
1566
+ const contractPath = resolveStaticContractPath(
1567
+ plugin,
1568
+ contract.contract,
1569
+ projectRoot
1570
+ );
1571
+ const relativePath = path.relative(projectRoot, contractPath);
1572
+ contracts.push({
1573
+ plugin: plugin.name,
1574
+ name: contract.name,
1575
+ type: "static",
1576
+ path: "./" + relativePath
1577
+ });
1578
+ }
1579
+ }
1580
+ if (manifest.dynamic_contracts) {
1581
+ const dynamicConfigs = Array.isArray(manifest.dynamic_contracts) ? manifest.dynamic_contracts : [manifest.dynamic_contracts];
1582
+ for (const config of dynamicConfigs) {
1583
+ contracts.push({
1584
+ plugin: plugin.name,
1585
+ name: `${config.prefix}/*`,
1586
+ type: "dynamic",
1587
+ path: "(run materialization to generate)"
1588
+ });
1589
+ }
1590
+ }
1591
+ }
1592
+ return {
1593
+ materialized_at: (/* @__PURE__ */ new Date()).toISOString(),
1594
+ jay_stack_version: getJayStackVersion(),
1595
+ contracts
1596
+ };
1597
+ }
1598
+ async function discoverPluginsWithSetup(options) {
1599
+ const { projectRoot, verbose, pluginFilter } = options;
1600
+ const allPlugins = await scanPlugins({
1601
+ projectRoot,
1602
+ verbose,
1603
+ discoverTransitive: true
1604
+ });
1605
+ const pluginsWithSetup = [];
1606
+ for (const [packageName, plugin] of allPlugins) {
1607
+ if (!plugin.manifest.setup?.handler)
1608
+ continue;
1609
+ if (pluginFilter && plugin.name !== pluginFilter && packageName !== pluginFilter) {
1610
+ continue;
1611
+ }
1612
+ pluginsWithSetup.push({
1613
+ name: plugin.name,
1614
+ pluginPath: plugin.pluginPath,
1615
+ packageName: plugin.packageName,
1616
+ isLocal: plugin.isLocal,
1617
+ setupHandler: plugin.manifest.setup.handler,
1618
+ setupDescription: plugin.manifest.setup.description,
1619
+ dependencies: plugin.dependencies
1620
+ });
1621
+ if (verbose) {
1622
+ getLogger().info(`[Setup] Found plugin with setup: ${plugin.name}`);
1623
+ }
1624
+ }
1625
+ return sortByDependencies(pluginsWithSetup);
1626
+ }
1627
+ async function discoverPluginsWithReferences(options) {
1628
+ const { projectRoot, verbose, pluginFilter } = options;
1629
+ const allPlugins = await scanPlugins({
1630
+ projectRoot,
1631
+ verbose,
1632
+ discoverTransitive: true
1633
+ });
1634
+ const pluginsWithRefs = [];
1635
+ for (const [packageName, plugin] of allPlugins) {
1636
+ if (!plugin.manifest.setup?.references)
1637
+ continue;
1638
+ if (pluginFilter && plugin.name !== pluginFilter && packageName !== pluginFilter) {
1639
+ continue;
1640
+ }
1641
+ pluginsWithRefs.push({
1642
+ name: plugin.name,
1643
+ pluginPath: plugin.pluginPath,
1644
+ packageName: plugin.packageName,
1645
+ isLocal: plugin.isLocal,
1646
+ referencesHandler: plugin.manifest.setup.references,
1647
+ dependencies: plugin.dependencies
1648
+ });
1649
+ if (verbose) {
1650
+ getLogger().info(`[AgentKit] Found plugin with references: ${plugin.name}`);
1651
+ }
1652
+ }
1653
+ return sortByDependencies(pluginsWithRefs);
1654
+ }
1655
+ function sortByDependencies(plugins) {
1656
+ return [...plugins].sort((a, b) => {
1657
+ const aDepsOnB = a.dependencies.some((d) => d === b.name || d === b.packageName) ? 1 : 0;
1658
+ const bDepsOnA = b.dependencies.some((d) => d === a.name || d === a.packageName) ? 1 : 0;
1659
+ return aDepsOnB - bDepsOnA;
1660
+ });
1661
+ }
1662
+ async function executePluginSetup(plugin, options) {
1663
+ const { projectRoot, configDir, force, initError, viteServer, verbose } = options;
1664
+ const context = {
1665
+ pluginName: plugin.name,
1666
+ projectRoot,
1667
+ configDir,
1668
+ services: getServiceRegistry(),
1669
+ initError,
1670
+ force
1671
+ };
1672
+ const handler = await loadHandler(plugin, plugin.setupHandler, viteServer);
1673
+ return handler(context);
1674
+ }
1675
+ async function executePluginReferences(plugin, options) {
1676
+ const { projectRoot, force, viteServer } = options;
1677
+ const referencesDir = path.join(projectRoot, "agent-kit", "references", plugin.name);
1678
+ const context = {
1679
+ pluginName: plugin.name,
1680
+ projectRoot,
1681
+ referencesDir,
1682
+ services: getServiceRegistry(),
1683
+ force
1684
+ };
1685
+ const handler = await loadHandler(
1686
+ plugin,
1687
+ plugin.referencesHandler,
1688
+ viteServer
1689
+ );
1690
+ return handler(context);
1691
+ }
1692
+ async function loadHandler(plugin, handlerName, viteServer) {
1693
+ let module;
1694
+ if (plugin.isLocal) {
1695
+ const handlerPath = path.resolve(plugin.pluginPath, handlerName);
1696
+ if (viteServer) {
1697
+ module = await viteServer.ssrLoadModule(handlerPath);
1698
+ } else {
1699
+ module = await import(handlerPath);
1700
+ }
1701
+ if (typeof module[handlerName] === "function")
1702
+ return module[handlerName];
1703
+ if (typeof module.setup === "function")
1704
+ return module.setup;
1705
+ if (typeof module.default === "function")
1706
+ return module.default;
1707
+ throw new Error(
1708
+ `Handler "${handlerName}" not found in "${plugin.pluginPath}". Available exports: ${Object.keys(module).join(", ")}`
1709
+ );
1710
+ } else {
1711
+ if (viteServer) {
1712
+ module = await viteServer.ssrLoadModule(plugin.packageName);
1713
+ } else {
1714
+ module = await import(plugin.packageName);
1715
+ }
1716
+ if (typeof module[handlerName] !== "function") {
1717
+ throw new Error(
1718
+ `Handler "${handlerName}" not found as export in "${plugin.packageName}". Available exports: ${Object.keys(module).join(", ")}`
1719
+ );
1720
+ }
1721
+ return module[handlerName];
1722
+ }
1723
+ }
964
1724
  export {
965
1725
  ActionRegistry,
966
1726
  DevSlowlyChangingPhase,
1727
+ SlowRenderCache,
967
1728
  actionRegistry,
968
1729
  clearActionRegistry,
969
1730
  clearClientInitData,
@@ -973,8 +1734,12 @@ export {
973
1734
  discoverAndRegisterActions,
974
1735
  discoverPluginActions,
975
1736
  discoverPluginsWithInit,
1737
+ discoverPluginsWithReferences,
1738
+ discoverPluginsWithSetup,
976
1739
  executeAction,
1740
+ executePluginReferences,
977
1741
  executePluginServerInits,
1742
+ executePluginSetup,
978
1743
  generateClientScript,
979
1744
  getActionCacheHeaders,
980
1745
  getClientInitData,
@@ -982,9 +1747,12 @@ export {
982
1747
  getRegisteredAction,
983
1748
  getRegisteredActionNames,
984
1749
  getService,
1750
+ getServiceRegistry,
985
1751
  hasAction,
986
1752
  hasService,
1753
+ listContracts,
987
1754
  loadPageParts,
1755
+ materializeContracts,
988
1756
  onInit,
989
1757
  onShutdown,
990
1758
  preparePluginClientInits,
@@ -996,6 +1764,8 @@ export {
996
1764
  runLoadParams,
997
1765
  runShutdownCallbacks,
998
1766
  runSlowlyChangingRender,
1767
+ scanPlugins,
999
1768
  setClientInitData,
1769
+ slowRenderInstances,
1000
1770
  sortPluginsByDependencies
1001
1771
  };