@jay-framework/stack-server-runtime 0.11.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 +337 -29
  2. package/dist/index.js +672 -89
  3. package/package.json +13 -11
package/dist/index.js CHANGED
@@ -17,6 +17,7 @@ import "js-beautify";
17
17
  import fs$2 from "fs";
18
18
  import path$1 from "path";
19
19
  import YAML from "yaml";
20
+ import { getLogger } from "@jay-framework/logger";
20
21
  import crypto from "node:crypto";
21
22
  const serviceRegistry = /* @__PURE__ */ new Map();
22
23
  function registerService(marker, service) {
@@ -40,9 +41,13 @@ function hasService(marker) {
40
41
  function clearServiceRegistry() {
41
42
  serviceRegistry.clear();
42
43
  }
44
+ function getServiceRegistry() {
45
+ return serviceRegistry;
46
+ }
43
47
  function resolveServices(serviceMarkers) {
44
48
  return serviceMarkers.map((marker) => getService(marker));
45
49
  }
50
+ globalThis.__JAY_SERVICE_RESOLVER__ = resolveServices;
46
51
  const initCallbacks = [];
47
52
  const shutdownCallbacks = [];
48
53
  function onInit(callback) {
@@ -94,10 +99,17 @@ class DevSlowlyChangingPhase {
94
99
  }
95
100
  async runSlowlyForPage(pageParams, pageProps, parts) {
96
101
  for (const part of parts) {
97
- const { compDefinition } = part;
102
+ const { compDefinition, contractInfo } = part;
98
103
  if (compDefinition.loadParams) {
99
104
  const services = resolveServices(compDefinition.services);
100
- 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);
101
113
  if (!await findMatchingParams(pageParams, compParams))
102
114
  return notFound();
103
115
  }
@@ -105,11 +117,19 @@ class DevSlowlyChangingPhase {
105
117
  let slowlyViewState = {};
106
118
  let carryForward = {};
107
119
  for (const part of parts) {
108
- const { compDefinition, key } = part;
120
+ const { compDefinition, key, contractInfo } = part;
109
121
  if (compDefinition.slowlyRender) {
110
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
+ };
111
131
  const slowlyRenderedPart = await compDefinition.slowlyRender(
112
- { ...pageProps, ...pageParams },
132
+ partProps,
113
133
  ...services
114
134
  );
115
135
  if (slowlyRenderedPart.kind === "PhaseOutput") {
@@ -136,12 +156,20 @@ async function renderFastChangingData(pageParams, pageProps, carryForward, parts
136
156
  let fastViewState = {};
137
157
  let fastCarryForward = {};
138
158
  for (const part of parts) {
139
- const { compDefinition, key } = part;
159
+ const { compDefinition, key, contractInfo } = part;
140
160
  if (compDefinition.fastRender) {
141
161
  const partSlowlyCarryForward = key ? carryForward[key] : carryForward;
142
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
+ };
143
171
  const fastRenderedPart = await compDefinition.fastRender(
144
- { ...pageProps, ...pageParams },
172
+ partProps,
145
173
  partSlowlyCarryForward,
146
174
  ...services
147
175
  );
@@ -234,13 +262,13 @@ ${clientInitExecution}
234
262
  const target = document.getElementById('target');
235
263
  const pageComp = makeCompositeJayComponent(render, viewState, fastCarryForward, ${compositeParts}, trackByMap)
236
264
 
237
- const instance = pageComp({...viewState, ...fastCarryForward})
265
+ const instance = pageComp({/* placeholder for page props */})
238
266
  ${automationWrap}
239
267
  <\/script>
240
268
  </body>
241
269
  </html>`;
242
270
  }
243
- const require$2 = createRequire(import.meta.url);
271
+ const require$3 = createRequire(import.meta.url);
244
272
  async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfig, options) {
245
273
  const exists = await fs$1.access(route.compPath, fs$1.constants.F_OK).then(() => true).catch(() => false);
246
274
  const parts = [];
@@ -268,11 +296,12 @@ async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfi
268
296
  );
269
297
  return jayHtmlWithValidations.mapAsync(async (jayHtml) => {
270
298
  const usedPackages = /* @__PURE__ */ new Set();
299
+ const headlessInstanceComponents = [];
271
300
  for await (const headlessImport of jayHtml.headlessImports) {
272
301
  const module = headlessImport.codeLink.module;
273
302
  const name = headlessImport.codeLink.names[0].name;
274
303
  const isLocalModule = module[0] === "." || module[0] === "/";
275
- 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) });
276
305
  const compDefinition = (await vite.ssrLoadModule(modulePath))[name];
277
306
  const moduleImport = isLocalModule ? path__default.resolve(dirName, module) : module;
278
307
  const isNpmPackage = !isLocalModule;
@@ -281,23 +310,85 @@ async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfi
281
310
  const packageName = module.startsWith("@") ? module.split("/").slice(0, 2).join("/") : module.split("/")[0];
282
311
  usedPackages.add(packageName);
283
312
  }
284
- const key = headlessImport.key;
285
- const part = {
286
- key,
287
- compDefinition,
288
- clientImport: `import {${name}} from '${clientModuleImport}'`,
289
- clientPart: `{comp: ${name}.comp, contextMarkers: ${name}.contexts || [], key: '${headlessImport.key}'}`
290
- };
291
- 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
+ }
292
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
+ }));
293
341
  return {
294
342
  parts,
295
343
  serverTrackByMap: jayHtml.serverTrackByMap,
296
344
  clientTrackByMap: jayHtml.clientTrackByMap,
297
- usedPackages
345
+ usedPackages,
346
+ headlessContracts,
347
+ headlessInstanceComponents
298
348
  };
299
349
  });
300
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
+ }
301
392
  class ActionRegistry {
302
393
  constructor() {
303
394
  __publicField(this, "actions", /* @__PURE__ */ new Map());
@@ -445,10 +536,6 @@ async function executeAction(actionName, input) {
445
536
  function getActionCacheHeaders(actionName) {
446
537
  return actionRegistry.getCacheHeaders(actionName);
447
538
  }
448
- async function runAction(action, input) {
449
- const services = resolveServices(action.services);
450
- return action.handler(input, ...services);
451
- }
452
539
  var __defProp2 = Object.defineProperty;
453
540
  var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
454
541
  var __publicField2 = (obj, key, value) => {
@@ -545,7 +632,7 @@ new Proxy(e, {
545
632
  return t[r];
546
633
  }
547
634
  });
548
- const require$1 = createRequire$1(import.meta.url);
635
+ const require$2 = createRequire$1(import.meta.url);
549
636
  async function discoverAndRegisterActions(options) {
550
637
  const {
551
638
  projectRoot,
@@ -562,13 +649,13 @@ async function discoverAndRegisterActions(options) {
562
649
  const actionsPath = path.resolve(projectRoot, actionsDir);
563
650
  if (!fs.existsSync(actionsPath)) {
564
651
  if (verbose) {
565
- console.log(`[Actions] No actions directory found at ${actionsPath}`);
652
+ getLogger().info(`[Actions] No actions directory found at ${actionsPath}`);
566
653
  }
567
654
  return result;
568
655
  }
569
656
  const actionFiles = await findActionFiles(actionsPath);
570
657
  if (verbose) {
571
- console.log(`[Actions] Found ${actionFiles.length} action file(s)`);
658
+ getLogger().info(`[Actions] Found ${actionFiles.length} action file(s)`);
572
659
  }
573
660
  for (const filePath of actionFiles) {
574
661
  result.scannedFiles.push(filePath);
@@ -585,12 +672,14 @@ async function discoverAndRegisterActions(options) {
585
672
  result.actionNames.push(exportValue.actionName);
586
673
  result.actionCount++;
587
674
  if (verbose) {
588
- console.log(`[Actions] Registered: ${exportValue.actionName}`);
675
+ getLogger().info(
676
+ `[Actions] Registered: ${exportValue.actionName}`
677
+ );
589
678
  }
590
679
  }
591
680
  }
592
681
  } catch (error) {
593
- console.error(`[Actions] Failed to import ${filePath}:`, error);
682
+ getLogger().error(`[Actions] Failed to import ${filePath}: ${error}`);
594
683
  }
595
684
  }
596
685
  return result;
@@ -657,9 +746,8 @@ async function discoverNpmPluginActions(projectRoot, registry, verbose, viteServ
657
746
  continue;
658
747
  }
659
748
  if (verbose) {
660
- console.log(
661
- `[Actions] NPM plugin "${packageName}" declares actions:`,
662
- pluginConfig.actions
749
+ getLogger().info(
750
+ `[Actions] NPM plugin "${packageName}" declares actions: ${JSON.stringify(pluginConfig.actions)}`
663
751
  );
664
752
  }
665
753
  const actions = await registerNpmPluginActions(
@@ -675,13 +763,13 @@ async function discoverNpmPluginActions(projectRoot, registry, verbose, viteServ
675
763
  }
676
764
  }
677
765
  } catch (error) {
678
- console.error("[Actions] Failed to read project package.json:", error);
766
+ getLogger().error(`[Actions] Failed to read project package.json: ${error}`);
679
767
  }
680
768
  return allActions;
681
769
  }
682
770
  function tryResolvePluginYaml(packageName, projectRoot) {
683
771
  try {
684
- return require$1.resolve(`${packageName}/plugin.yaml`, {
772
+ return require$2.resolve(`${packageName}/plugin.yaml`, {
685
773
  paths: [projectRoot]
686
774
  });
687
775
  } catch {
@@ -703,18 +791,18 @@ async function registerNpmPluginActions(packageName, pluginConfig, registry, ver
703
791
  registry.register(actionExport);
704
792
  registeredActions.push(actionExport.actionName);
705
793
  if (verbose) {
706
- console.log(
794
+ getLogger().info(
707
795
  `[Actions] Registered NPM plugin action: ${actionExport.actionName}`
708
796
  );
709
797
  }
710
798
  } else {
711
- console.warn(
799
+ getLogger().warn(
712
800
  `[Actions] NPM plugin "${packageName}" declares action "${actionName}" but it's not exported or not a JayAction`
713
801
  );
714
802
  }
715
803
  }
716
804
  } catch (importError) {
717
- console.error(`[Actions] Failed to import NPM plugin "${packageName}":`, importError);
805
+ getLogger().error(`[Actions] Failed to import NPM plugin "${packageName}": ${importError}`);
718
806
  }
719
807
  return registeredActions;
720
808
  }
@@ -729,7 +817,9 @@ async function discoverPluginActions(pluginPath, projectRoot, registry = actionR
729
817
  const registeredActions = [];
730
818
  const pluginName = pluginConfig.name || path.basename(pluginPath);
731
819
  if (verbose) {
732
- console.log(`[Actions] Plugin "${pluginName}" declares actions:`, pluginConfig.actions);
820
+ getLogger().info(
821
+ `[Actions] Plugin "${pluginName}" declares actions: ${JSON.stringify(pluginConfig.actions)}`
822
+ );
733
823
  }
734
824
  let modulePath = pluginConfig.module ? path.join(pluginPath, pluginConfig.module) : path.join(pluginPath, "index.ts");
735
825
  if (!fs.existsSync(modulePath)) {
@@ -740,7 +830,7 @@ async function discoverPluginActions(pluginPath, projectRoot, registry = actionR
740
830
  } else if (fs.existsSync(jsPath)) {
741
831
  modulePath = jsPath;
742
832
  } else {
743
- console.warn(`[Actions] Plugin "${pluginName}" module not found at ${modulePath}`);
833
+ getLogger().warn(`[Actions] Plugin "${pluginName}" module not found at ${modulePath}`);
744
834
  return [];
745
835
  }
746
836
  }
@@ -757,25 +847,32 @@ async function discoverPluginActions(pluginPath, projectRoot, registry = actionR
757
847
  registry.register(actionExport);
758
848
  registeredActions.push(actionExport.actionName);
759
849
  if (verbose) {
760
- console.log(
850
+ getLogger().info(
761
851
  `[Actions] Registered plugin action: ${actionExport.actionName}`
762
852
  );
763
853
  }
764
854
  } else {
765
- console.warn(
855
+ getLogger().warn(
766
856
  `[Actions] Plugin "${pluginName}" declares action "${actionName}" but it's not exported or not a JayAction`
767
857
  );
768
858
  }
769
859
  }
770
860
  } catch (importError) {
771
- 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
+ );
772
864
  }
773
865
  return registeredActions;
774
866
  }
775
- const require2 = createRequire$1(import.meta.url);
776
- async function discoverPluginsWithInit(options) {
777
- const { projectRoot, verbose = false } = options;
778
- 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();
779
876
  const visitedPackages = /* @__PURE__ */ new Set();
780
877
  const localPluginsPath = path.join(projectRoot, "src/plugins");
781
878
  if (fs.existsSync(localPluginsPath)) {
@@ -788,37 +885,37 @@ async function discoverPluginsWithInit(options) {
788
885
  const manifest = loadPluginManifest(pluginPath);
789
886
  if (!manifest)
790
887
  continue;
791
- const initConfig = resolvePluginInit(pluginPath, manifest.init, true);
792
- if (!initConfig)
793
- continue;
794
888
  const dependencies = await getPackageDependencies(pluginPath);
795
- plugins.push({
796
- name: manifest.name || entry.name,
889
+ const pluginName = manifest.name || entry.name;
890
+ plugins.set(entry.name, {
891
+ name: pluginName,
797
892
  pluginPath,
798
893
  packageName: pluginPath,
799
- // For local, use path
894
+ // For local, use path as identifier
800
895
  isLocal: true,
801
- initModule: initConfig.module,
802
- initExport: initConfig.export,
896
+ manifest,
803
897
  dependencies
804
898
  });
805
899
  visitedPackages.add(pluginPath);
806
900
  if (verbose) {
807
- console.log(
808
- `[PluginInit] Found local plugin with init: ${manifest.name || entry.name}`
809
- );
901
+ getLogger().info(`[PluginScanner] Found local plugin: ${pluginName}`);
810
902
  }
811
903
  }
812
904
  } catch (error) {
813
- console.warn(`[PluginInit] Failed to scan local plugins: ${error}`);
905
+ if (verbose) {
906
+ getLogger().warn(`[PluginScanner] Failed to scan local plugins: ${error}`);
907
+ }
814
908
  }
815
909
  }
816
910
  const projectPackageJsonPath = path.join(projectRoot, "package.json");
817
911
  if (fs.existsSync(projectPackageJsonPath)) {
818
912
  try {
819
913
  const projectPackageJson = JSON.parse(fs.readFileSync(projectPackageJsonPath, "utf-8"));
820
- const initialDeps = Object.keys(projectPackageJson.dependencies || {});
821
- const packagesToCheck = [...initialDeps];
914
+ const deps = {
915
+ ...projectPackageJson.dependencies,
916
+ ...includeDevDeps ? projectPackageJson.devDependencies : {}
917
+ };
918
+ const packagesToCheck = Object.keys(deps);
822
919
  while (packagesToCheck.length > 0) {
823
920
  const depName = packagesToCheck.shift();
824
921
  if (visitedPackages.has(depName))
@@ -826,7 +923,7 @@ async function discoverPluginsWithInit(options) {
826
923
  visitedPackages.add(depName);
827
924
  let pluginYamlPath;
828
925
  try {
829
- pluginYamlPath = require2.resolve(`${depName}/plugin.yaml`, {
926
+ pluginYamlPath = require$1.resolve(`${depName}/plugin.yaml`, {
830
927
  paths: [projectRoot]
831
928
  });
832
929
  } catch {
@@ -836,30 +933,76 @@ async function discoverPluginsWithInit(options) {
836
933
  const manifest = loadPluginManifest(pluginPath);
837
934
  if (!manifest)
838
935
  continue;
839
- const initConfig = resolvePluginInit(pluginPath, manifest.init, false);
840
- if (!initConfig)
841
- continue;
842
936
  const dependencies = await getPackageDependencies(pluginPath);
843
- plugins.push({
937
+ plugins.set(depName, {
844
938
  name: manifest.name || depName,
845
939
  pluginPath,
846
940
  packageName: depName,
847
941
  isLocal: false,
848
- initModule: initConfig.module,
849
- initExport: initConfig.export,
942
+ manifest,
850
943
  dependencies
851
944
  });
852
945
  if (verbose) {
853
- console.log(`[PluginInit] Found NPM plugin with init: ${depName}`);
946
+ getLogger().info(`[PluginScanner] Found NPM plugin: ${depName}`);
854
947
  }
855
- for (const transitiveDep of dependencies) {
856
- if (!visitedPackages.has(transitiveDep)) {
857
- packagesToCheck.push(transitiveDep);
948
+ if (discoverTransitive) {
949
+ for (const transitiveDep of dependencies) {
950
+ if (!visitedPackages.has(transitiveDep)) {
951
+ packagesToCheck.push(transitiveDep);
952
+ }
858
953
  }
859
954
  }
860
955
  }
861
956
  } catch (error) {
862
- 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}`);
863
1006
  }
864
1007
  }
865
1008
  return plugins;
@@ -899,18 +1042,6 @@ function resolvePluginInit(pluginPath, initConfig, isLocal) {
899
1042
  return null;
900
1043
  }
901
1044
  }
902
- async function getPackageDependencies(pluginPath) {
903
- const packageJsonPath = path.join(pluginPath, "package.json");
904
- if (!fs.existsSync(packageJsonPath)) {
905
- return [];
906
- }
907
- try {
908
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
909
- return Object.keys(packageJson.dependencies || {});
910
- } catch {
911
- return [];
912
- }
913
- }
914
1045
  function sortPluginsByDependencies(plugins) {
915
1046
  const pluginNames = new Set(plugins.map((p) => p.packageName));
916
1047
  const sorted = [];
@@ -920,7 +1051,7 @@ function sortPluginsByDependencies(plugins) {
920
1051
  if (visited.has(plugin.packageName))
921
1052
  return;
922
1053
  if (visiting.has(plugin.packageName)) {
923
- console.warn(`[PluginInit] Circular dependency detected for ${plugin.name}`);
1054
+ getLogger().warn(`[PluginInit] Circular dependency detected for ${plugin.name}`);
924
1055
  return;
925
1056
  }
926
1057
  visiting.add(plugin.packageName);
@@ -960,14 +1091,14 @@ async function executePluginServerInits(plugins, viteServer, verbose = false) {
960
1091
  }
961
1092
  const jayInit = pluginModule[plugin.initExport];
962
1093
  if (!jayInit || jayInit.__brand !== "JayInit") {
963
- console.warn(
1094
+ getLogger().warn(
964
1095
  `[PluginInit] Plugin "${plugin.name}" init module doesn't export a valid JayInit at "${plugin.initExport}"`
965
1096
  );
966
1097
  continue;
967
1098
  }
968
1099
  if (typeof jayInit._serverInit === "function") {
969
1100
  if (verbose) {
970
- console.log(`[DevServer] Running server init: ${plugin.name}`);
1101
+ getLogger().info(`[DevServer] Running server init: ${plugin.name}`);
971
1102
  }
972
1103
  const clientData = await jayInit._serverInit();
973
1104
  if (clientData !== void 0 && clientData !== null) {
@@ -975,9 +1106,8 @@ async function executePluginServerInits(plugins, viteServer, verbose = false) {
975
1106
  }
976
1107
  }
977
1108
  } catch (error) {
978
- console.error(
979
- `[PluginInit] Failed to execute server init for "${plugin.name}":`,
980
- error
1109
+ getLogger().error(
1110
+ `[PluginInit] Failed to execute server init for "${plugin.name}": ${error}`
981
1111
  );
982
1112
  }
983
1113
  }
@@ -1146,6 +1276,451 @@ class SlowRenderCache {
1146
1276
  return Array.from(this.pathToKeys.keys());
1147
1277
  }
1148
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
+ }
1149
1724
  export {
1150
1725
  ActionRegistry,
1151
1726
  DevSlowlyChangingPhase,
@@ -1159,8 +1734,12 @@ export {
1159
1734
  discoverAndRegisterActions,
1160
1735
  discoverPluginActions,
1161
1736
  discoverPluginsWithInit,
1737
+ discoverPluginsWithReferences,
1738
+ discoverPluginsWithSetup,
1162
1739
  executeAction,
1740
+ executePluginReferences,
1163
1741
  executePluginServerInits,
1742
+ executePluginSetup,
1164
1743
  generateClientScript,
1165
1744
  getActionCacheHeaders,
1166
1745
  getClientInitData,
@@ -1168,9 +1747,12 @@ export {
1168
1747
  getRegisteredAction,
1169
1748
  getRegisteredActionNames,
1170
1749
  getService,
1750
+ getServiceRegistry,
1171
1751
  hasAction,
1172
1752
  hasService,
1753
+ listContracts,
1173
1754
  loadPageParts,
1755
+ materializeContracts,
1174
1756
  onInit,
1175
1757
  onShutdown,
1176
1758
  preparePluginClientInits,
@@ -1178,11 +1760,12 @@ export {
1178
1760
  registerService,
1179
1761
  renderFastChangingData,
1180
1762
  resolveServices,
1181
- runAction,
1182
1763
  runInitCallbacks,
1183
1764
  runLoadParams,
1184
1765
  runShutdownCallbacks,
1185
1766
  runSlowlyChangingRender,
1767
+ scanPlugins,
1186
1768
  setClientInitData,
1769
+ slowRenderInstances,
1187
1770
  sortPluginsByDependencies
1188
1771
  };