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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +536 -4
  2. package/dist/index.js +993 -36
  3. package/package.json +11 -9
package/dist/index.js CHANGED
@@ -1,8 +1,23 @@
1
- import { notFound, partialRender } from "@jay-framework/fullstack-component";
2
- import fs from "node:fs/promises";
3
- import path from "node:path";
4
- import { parseJayFile } from "@jay-framework/compiler-jay-html";
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => {
4
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
+ return value;
6
+ };
7
+ import { notFound, partialRender, phaseOutput, isJayAction } from "@jay-framework/fullstack-component";
8
+ import fs$1 from "node:fs/promises";
9
+ import * as path from "node:path";
10
+ import path__default from "node:path";
11
+ import { parseJayFile, JAY_IMPORT_RESOLVER } from "@jay-framework/compiler-jay-html";
5
12
  import { createRequire } from "module";
13
+ import * as fs from "node:fs";
14
+ import { createRequire as createRequire$1 } from "node:module";
15
+ import "prettier";
16
+ import "js-beautify";
17
+ import fs$2 from "fs";
18
+ import path$1 from "path";
19
+ import YAML from "yaml";
20
+ import crypto from "node:crypto";
6
21
  const serviceRegistry = /* @__PURE__ */ new Map();
7
22
  function registerService(marker, service) {
8
23
  serviceRegistry.set(marker, service);
@@ -50,6 +65,19 @@ function clearLifecycleCallbacks() {
50
65
  initCallbacks.length = 0;
51
66
  shutdownCallbacks.length = 0;
52
67
  }
68
+ let clientInitData = {};
69
+ function setClientInitData(key, data) {
70
+ clientInitData[key] = { ...clientInitData[key] || {}, ...data };
71
+ }
72
+ function getClientInitData() {
73
+ return clientInitData;
74
+ }
75
+ function getClientInitDataForKey(key) {
76
+ return clientInitData[key] || {};
77
+ }
78
+ function clearClientInitData() {
79
+ clientInitData = {};
80
+ }
53
81
  function isLeftSideParamsSubsetOfRightSideParams(left, right) {
54
82
  return Object.keys(left).reduce((prev, curr) => prev && left[curr] === right[curr], true);
55
83
  }
@@ -84,7 +112,7 @@ class DevSlowlyChangingPhase {
84
112
  { ...pageProps, ...pageParams },
85
113
  ...services
86
114
  );
87
- if (slowlyRenderedPart.kind === "PartialRender") {
115
+ if (slowlyRenderedPart.kind === "PhaseOutput") {
88
116
  if (!key) {
89
117
  slowlyViewState = { ...slowlyViewState, ...slowlyRenderedPart.rendered };
90
118
  carryForward = { ...carryForward, ...slowlyRenderedPart.carryForward };
@@ -117,7 +145,7 @@ async function renderFastChangingData(pageParams, pageProps, carryForward, parts
117
145
  partSlowlyCarryForward,
118
146
  ...services
119
147
  );
120
- if (fastRenderedPart.kind === "PartialRender") {
148
+ if (fastRenderedPart.kind === "PhaseOutput") {
121
149
  if (!key) {
122
150
  fastViewState = { ...fastViewState, ...fastRenderedPart.rendered };
123
151
  fastCarryForward = { ...fastCarryForward, ...fastRenderedPart.carryForward };
@@ -129,13 +157,60 @@ async function renderFastChangingData(pageParams, pageProps, carryForward, parts
129
157
  return fastRenderedPart;
130
158
  }
131
159
  }
132
- return Promise.resolve(partialRender(fastViewState, fastCarryForward));
160
+ return Promise.resolve(phaseOutput(fastViewState, fastCarryForward));
133
161
  }
134
- function generateClientScript(defaultViewState, fastCarryForward, parts, jayHtmlPath) {
162
+ function generateClientScript(defaultViewState, fastCarryForward, parts, jayHtmlPath, trackByMap = {}, clientInitData2 = {}, projectInit, pluginInits = [], options = {}) {
163
+ const { enableAutomation = true, slowViewState } = options;
164
+ const hasSlowViewState = slowViewState && Object.keys(slowViewState).length > 0;
135
165
  const imports = parts.length > 0 ? parts.map((part) => part.clientImport).join("\n") + "\n" : "";
136
166
  const compositeParts = parts.length > 0 ? `[
137
167
  ${parts.map((part) => " " + part.clientPart).join(",\n")}
138
168
  ]` : "[]";
169
+ const hasClientInit = projectInit || pluginInits.length > 0;
170
+ const pluginClientInitImports = pluginInits.map((plugin, idx) => {
171
+ return `import { ${plugin.initExport} as jayInit${idx} } from "${plugin.importPath}";`;
172
+ }).join("\n ");
173
+ const projectInitImport = projectInit ? `import { ${projectInit.initExport || "init"} as projectJayInit } from "${projectInit.importPath}";` : "";
174
+ const pluginClientInitCalls = pluginInits.map((plugin, idx) => {
175
+ const pluginData = clientInitData2[plugin.name] || {};
176
+ return `if (typeof jayInit${idx}._clientInit === 'function') {
177
+ console.log('[DevServer] Running client init: ${plugin.name}');
178
+ await jayInit${idx}._clientInit(${JSON.stringify(pluginData)});
179
+ }`;
180
+ }).join("\n ");
181
+ const projectInitCall = projectInit ? `if (typeof projectJayInit._clientInit === 'function') {
182
+ console.log('[DevServer] Running client init: project');
183
+ const projectData = ${JSON.stringify(clientInitData2["project"] || {})};
184
+ await projectJayInit._clientInit(projectData);
185
+ }` : "";
186
+ const clientInitExecution = hasClientInit ? `
187
+ // Plugin client initialization (in dependency order)
188
+ ${pluginClientInitCalls}
189
+
190
+ // Project client initialization
191
+ ${projectInitCall}
192
+ ` : "";
193
+ const automationImport = enableAutomation ? hasSlowViewState ? `import { wrapWithAutomation, AUTOMATION_CONTEXT } from "@jay-framework/runtime-automation";
194
+ import { registerGlobalContext } from "@jay-framework/runtime";
195
+ import { deepMergeViewStates } from "@jay-framework/view-state-merge";` : `import { wrapWithAutomation, AUTOMATION_CONTEXT } from "@jay-framework/runtime-automation";
196
+ import { registerGlobalContext } from "@jay-framework/runtime";` : "";
197
+ const slowViewStateDecl = enableAutomation && hasSlowViewState ? `const slowViewState = ${JSON.stringify(slowViewState)};` : "";
198
+ const automationWrap = enableAutomation ? hasSlowViewState ? `
199
+ // Wrap with automation for dev tooling
200
+ // Deep merge slow+fast ViewState so automation can see full page state
201
+ const fullViewState = deepMergeViewStates(slowViewState, {...viewState, ...fastCarryForward}, trackByMap);
202
+ const wrapped = wrapWithAutomation(instance, { initialViewState: fullViewState, trackByMap });
203
+ registerGlobalContext(AUTOMATION_CONTEXT, wrapped.automation);
204
+ window.__jay = window.__jay || {};
205
+ window.__jay.automation = wrapped.automation;
206
+ target.appendChild(wrapped.element.dom);` : `
207
+ // Wrap with automation for dev tooling
208
+ const wrapped = wrapWithAutomation(instance);
209
+ registerGlobalContext(AUTOMATION_CONTEXT, wrapped.automation);
210
+ window.__jay = window.__jay || {};
211
+ window.__jay.automation = wrapped.automation;
212
+ target.appendChild(wrapped.element.dom);` : `
213
+ target.appendChild(instance.element.dom);`;
139
214
  return `<!doctype html>
140
215
  <html lang="en">
141
216
  <head>
@@ -147,38 +222,40 @@ ${parts.map((part) => " " + part.clientPart).join(",\n")}
147
222
  <div id="target"></div>
148
223
  <script type="module">
149
224
  import {makeCompositeJayComponent} from "@jay-framework/stack-client-runtime";
225
+ ${automationImport}
226
+ ${pluginClientInitImports}
227
+ ${projectInitImport}
150
228
  import { render } from '${jayHtmlPath}';
151
- ${imports}
229
+ ${imports}${slowViewStateDecl}
152
230
  const viewState = ${JSON.stringify(defaultViewState)};
153
231
  const fastCarryForward = ${JSON.stringify(fastCarryForward)};
154
-
232
+ const trackByMap = ${JSON.stringify(trackByMap)};
233
+ ${clientInitExecution}
155
234
  const target = document.getElementById('target');
156
- const pageComp = makeCompositeJayComponent(render, viewState, fastCarryForward, ${compositeParts})
235
+ const pageComp = makeCompositeJayComponent(render, viewState, fastCarryForward, ${compositeParts}, trackByMap)
157
236
 
158
237
  const instance = pageComp({...viewState, ...fastCarryForward})
159
- target.appendChild(instance.element.dom);
238
+ ${automationWrap}
160
239
  <\/script>
161
240
  </body>
162
241
  </html>`;
163
242
  }
164
- const require2 = createRequire(import.meta.url);
165
- async function loadPageParts(vite, route, pagesBase, jayRollupConfig) {
166
- const exists = await fs.access(route.compPath, fs.constants.F_OK).then(() => true).catch(() => false);
243
+ const require$2 = createRequire(import.meta.url);
244
+ async function loadPageParts(vite, route, pagesBase, projectBase, jayRollupConfig, options) {
245
+ const exists = await fs$1.access(route.compPath, fs$1.constants.F_OK).then(() => true).catch(() => false);
167
246
  const parts = [];
168
247
  if (exists) {
169
- const pageComponent = (await vite.ssrLoadModule(route.compPath + "?jay-server")).page;
248
+ const pageComponent = (await vite.ssrLoadModule(route.compPath)).page;
170
249
  parts.push({
171
250
  compDefinition: pageComponent,
172
- // Client import uses client-only code (server code stripped)
173
- clientImport: `import {page} from '${route.compPath}?jay-client'`,
174
- clientPart: `{comp: page.comp, contextMarkers: []}`
251
+ clientImport: `import {page} from '${route.compPath}'`,
252
+ clientPart: `{comp: page.comp, contextMarkers: page.contexts || []}`
175
253
  });
176
254
  }
177
- const jayHtmlSource = (await fs.readFile(route.jayHtmlPath)).toString();
178
- const fileName = path.basename(route.jayHtmlPath);
179
- const dirName = path.dirname(route.jayHtmlPath);
180
- const module = await import("@jay-framework/compiler-jay-html");
181
- const JAY_IMPORT_RESOLVER = module.JAY_IMPORT_RESOLVER;
255
+ const jayHtmlFilePath = options?.preRenderedPath ?? route.jayHtmlPath;
256
+ const jayHtmlSource = (await fs$1.readFile(jayHtmlFilePath)).toString();
257
+ const fileName = path__default.basename(route.jayHtmlPath);
258
+ const dirName = path__default.dirname(route.jayHtmlPath);
182
259
  const jayHtmlWithValidations = await parseJayFile(
183
260
  jayHtmlSource,
184
261
  fileName,
@@ -186,46 +263,926 @@ async function loadPageParts(vite, route, pagesBase, jayRollupConfig) {
186
263
  {
187
264
  relativePath: jayRollupConfig.tsConfigFilePath
188
265
  },
189
- JAY_IMPORT_RESOLVER
266
+ JAY_IMPORT_RESOLVER,
267
+ projectBase
190
268
  );
191
269
  return jayHtmlWithValidations.mapAsync(async (jayHtml) => {
270
+ const usedPackages = /* @__PURE__ */ new Set();
192
271
  for await (const headlessImport of jayHtml.headlessImports) {
193
- const module2 = headlessImport.codeLink.module;
272
+ const module = headlessImport.codeLink.module;
194
273
  const name = headlessImport.codeLink.names[0].name;
195
- const isLocalModule = module2[0] === "." || module2[0] === "/";
196
- const modulePath = isLocalModule ? path.resolve(dirName, module2) : require2.resolve(module2, { paths: require2.resolve.paths(dirName) });
197
- const serverModulePath = isLocalModule ? modulePath + "?jay-server" : modulePath;
198
- const compDefinition = (await vite.ssrLoadModule(serverModulePath))[name];
199
- const moduleImport = module2.startsWith("./") ? path.resolve(pagesBase, module2) : module2;
200
- const isNpmPackage = !module2.startsWith("./") && !module2.startsWith("../");
201
- const clientModuleImport = isNpmPackage ? `${moduleImport}/client` : `${moduleImport}?jay-client`;
274
+ 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) });
276
+ const compDefinition = (await vite.ssrLoadModule(modulePath))[name];
277
+ const moduleImport = isLocalModule ? path__default.resolve(dirName, module) : module;
278
+ const isNpmPackage = !isLocalModule;
279
+ const clientModuleImport = isNpmPackage ? `${moduleImport}/client` : `${moduleImport}`;
280
+ if (isNpmPackage) {
281
+ const packageName = module.startsWith("@") ? module.split("/").slice(0, 2).join("/") : module.split("/")[0];
282
+ usedPackages.add(packageName);
283
+ }
202
284
  const key = headlessImport.key;
203
285
  const part = {
204
286
  key,
205
287
  compDefinition,
206
288
  clientImport: `import {${name}} from '${clientModuleImport}'`,
207
- clientPart: `{comp: ${name}.comp, contextMarkers: [], key: '${headlessImport.key}'}`
289
+ clientPart: `{comp: ${name}.comp, contextMarkers: ${name}.contexts || [], key: '${headlessImport.key}'}`
208
290
  };
209
291
  parts.push(part);
210
292
  }
211
- return parts;
293
+ return {
294
+ parts,
295
+ serverTrackByMap: jayHtml.serverTrackByMap,
296
+ clientTrackByMap: jayHtml.clientTrackByMap,
297
+ usedPackages
298
+ };
299
+ });
300
+ }
301
+ class ActionRegistry {
302
+ constructor() {
303
+ __publicField(this, "actions", /* @__PURE__ */ new Map());
304
+ }
305
+ /**
306
+ * Registers an action with the registry.
307
+ *
308
+ * @param action - The JayAction to register (created via makeJayAction/makeJayQuery)
309
+ */
310
+ register(action) {
311
+ const entry = {
312
+ actionName: action.actionName,
313
+ method: action.method,
314
+ cacheOptions: action.cacheOptions,
315
+ services: action.services,
316
+ handler: action.handler
317
+ };
318
+ this.actions.set(action.actionName, entry);
319
+ }
320
+ /**
321
+ * Retrieves a registered action by name.
322
+ *
323
+ * @param actionName - The unique action name
324
+ * @returns The registered action or undefined
325
+ */
326
+ get(actionName) {
327
+ return this.actions.get(actionName);
328
+ }
329
+ /**
330
+ * Checks if an action is registered.
331
+ *
332
+ * @param actionName - The unique action name
333
+ * @returns true if the action is registered
334
+ */
335
+ has(actionName) {
336
+ return this.actions.has(actionName);
337
+ }
338
+ /**
339
+ * Gets all registered action names.
340
+ *
341
+ * @returns Array of registered action names
342
+ */
343
+ getNames() {
344
+ return Array.from(this.actions.keys());
345
+ }
346
+ /**
347
+ * Clears all registered actions.
348
+ */
349
+ clear() {
350
+ this.actions.clear();
351
+ }
352
+ /**
353
+ * Executes a registered action with the given input.
354
+ * Resolves services and calls the handler.
355
+ *
356
+ * @param actionName - The action to execute
357
+ * @param input - The input data for the action
358
+ * @returns The action result or error
359
+ */
360
+ async execute(actionName, input) {
361
+ const action = this.actions.get(actionName);
362
+ if (!action) {
363
+ return {
364
+ success: false,
365
+ error: {
366
+ code: "ACTION_NOT_FOUND",
367
+ message: `Action '${actionName}' is not registered`,
368
+ isActionError: false
369
+ }
370
+ };
371
+ }
372
+ try {
373
+ const services = resolveServices(action.services);
374
+ const result = await action.handler(input, ...services);
375
+ return {
376
+ success: true,
377
+ data: result
378
+ };
379
+ } catch (error) {
380
+ if (error && typeof error === "object" && "code" in error && "message" in error) {
381
+ const actionError = error;
382
+ if (actionError.name === "ActionError") {
383
+ return {
384
+ success: false,
385
+ error: {
386
+ code: actionError.code,
387
+ message: actionError.message,
388
+ isActionError: true
389
+ }
390
+ };
391
+ }
392
+ }
393
+ const message = error instanceof Error ? error.message : "Unknown error occurred";
394
+ return {
395
+ success: false,
396
+ error: {
397
+ code: "INTERNAL_ERROR",
398
+ message,
399
+ isActionError: false
400
+ }
401
+ };
402
+ }
403
+ }
404
+ /**
405
+ * Gets the cache headers for an action (if applicable).
406
+ *
407
+ * @param actionName - The action name
408
+ * @returns Cache-Control header value or undefined
409
+ */
410
+ getCacheHeaders(actionName) {
411
+ const action = this.actions.get(actionName);
412
+ if (!action || action.method !== "GET" || !action.cacheOptions) {
413
+ return void 0;
414
+ }
415
+ const { maxAge, staleWhileRevalidate } = action.cacheOptions;
416
+ const parts = [];
417
+ if (maxAge !== void 0) {
418
+ parts.push(`max-age=${maxAge}`);
419
+ }
420
+ if (staleWhileRevalidate !== void 0) {
421
+ parts.push(`stale-while-revalidate=${staleWhileRevalidate}`);
422
+ }
423
+ return parts.length > 0 ? parts.join(", ") : void 0;
424
+ }
425
+ }
426
+ const actionRegistry = new ActionRegistry();
427
+ function registerAction(action) {
428
+ actionRegistry.register(action);
429
+ }
430
+ function getRegisteredAction(actionName) {
431
+ return actionRegistry.get(actionName);
432
+ }
433
+ function hasAction(actionName) {
434
+ return actionRegistry.has(actionName);
435
+ }
436
+ function getRegisteredActionNames() {
437
+ return actionRegistry.getNames();
438
+ }
439
+ function clearActionRegistry() {
440
+ actionRegistry.clear();
441
+ }
442
+ async function executeAction(actionName, input) {
443
+ return actionRegistry.execute(actionName, input);
444
+ }
445
+ function getActionCacheHeaders(actionName) {
446
+ return actionRegistry.getCacheHeaders(actionName);
447
+ }
448
+ async function runAction(action, input) {
449
+ const services = resolveServices(action.services);
450
+ return action.handler(input, ...services);
451
+ }
452
+ var __defProp2 = Object.defineProperty;
453
+ var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
454
+ var __publicField2 = (obj, key, value) => {
455
+ __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
456
+ return value;
457
+ };
458
+ class JayAtomicType {
459
+ constructor(name) {
460
+ __publicField2(this, "kind", 0);
461
+ this.name = name;
462
+ }
463
+ }
464
+ new JayAtomicType("string");
465
+ new JayAtomicType("number");
466
+ new JayAtomicType("boolean");
467
+ new JayAtomicType("Date");
468
+ new JayAtomicType("Unknown");
469
+ class JayObjectType {
470
+ constructor(name, props) {
471
+ __publicField2(this, "kind", 8);
472
+ this.name = name;
473
+ this.props = props;
474
+ }
475
+ }
476
+ new JayObjectType("Error", {
477
+ message: new JayAtomicType("string"),
478
+ name: new JayAtomicType("string"),
479
+ stack: new JayAtomicType("string")
480
+ });
481
+ var RuntimeMode = /* @__PURE__ */ ((RuntimeMode2) => {
482
+ RuntimeMode2["MainTrusted"] = "mainTrusted";
483
+ RuntimeMode2["MainSandbox"] = "mainSandbox";
484
+ RuntimeMode2["WorkerTrusted"] = "workerTrusted";
485
+ RuntimeMode2["WorkerSandbox"] = "workerSandbox";
486
+ return RuntimeMode2;
487
+ })(RuntimeMode || {});
488
+ const TS_EXTENSION = ".ts";
489
+ const JAY_QUERY_PREFIX = "?jay-";
490
+ [
491
+ // Build environments
492
+ {
493
+ pattern: `${JAY_QUERY_PREFIX}${"client"}`,
494
+ buildEnv: "client"
495
+ /* Client */
496
+ },
497
+ {
498
+ pattern: `${JAY_QUERY_PREFIX}${"server"}`,
499
+ buildEnv: "server"
500
+ /* Server */
501
+ },
502
+ // Runtime modes (with .ts suffix)
503
+ {
504
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.MainSandbox}${TS_EXTENSION}`,
505
+ runtimeMode: RuntimeMode.MainSandbox
506
+ },
507
+ {
508
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerTrusted}${TS_EXTENSION}`,
509
+ runtimeMode: RuntimeMode.WorkerTrusted
510
+ },
511
+ {
512
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerSandbox}${TS_EXTENSION}`,
513
+ runtimeMode: RuntimeMode.WorkerSandbox
514
+ },
515
+ // Runtime modes (without .ts suffix)
516
+ {
517
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.MainSandbox}`,
518
+ runtimeMode: RuntimeMode.MainSandbox
519
+ },
520
+ {
521
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerTrusted}`,
522
+ runtimeMode: RuntimeMode.WorkerTrusted
523
+ },
524
+ {
525
+ pattern: `${JAY_QUERY_PREFIX}${RuntimeMode.WorkerSandbox}`,
526
+ runtimeMode: RuntimeMode.WorkerSandbox
527
+ }
528
+ ];
529
+ createRequire(import.meta.url);
530
+ function loadPluginManifest(pluginDir) {
531
+ const pluginYamlPath = path$1.join(pluginDir, "plugin.yaml");
532
+ if (!fs$2.existsSync(pluginYamlPath)) {
533
+ return null;
534
+ }
535
+ try {
536
+ const yamlContent = fs$2.readFileSync(pluginYamlPath, "utf-8");
537
+ return YAML.parse(yamlContent);
538
+ } catch (error) {
539
+ return null;
540
+ }
541
+ }
542
+ const s = createRequire(import.meta.url), e = s("typescript");
543
+ new Proxy(e, {
544
+ get(t, r) {
545
+ return t[r];
546
+ }
547
+ });
548
+ const require$1 = createRequire$1(import.meta.url);
549
+ async function discoverAndRegisterActions(options) {
550
+ const {
551
+ projectRoot,
552
+ actionsDir = "src/actions",
553
+ registry = actionRegistry,
554
+ verbose = false,
555
+ viteServer
556
+ } = options;
557
+ const result = {
558
+ actionCount: 0,
559
+ actionNames: [],
560
+ scannedFiles: []
561
+ };
562
+ const actionsPath = path.resolve(projectRoot, actionsDir);
563
+ if (!fs.existsSync(actionsPath)) {
564
+ if (verbose) {
565
+ console.log(`[Actions] No actions directory found at ${actionsPath}`);
566
+ }
567
+ return result;
568
+ }
569
+ const actionFiles = await findActionFiles(actionsPath);
570
+ if (verbose) {
571
+ console.log(`[Actions] Found ${actionFiles.length} action file(s)`);
572
+ }
573
+ for (const filePath of actionFiles) {
574
+ result.scannedFiles.push(filePath);
575
+ try {
576
+ let module;
577
+ if (viteServer) {
578
+ module = await viteServer.ssrLoadModule(filePath);
579
+ } else {
580
+ module = await import(filePath);
581
+ }
582
+ for (const [exportName, exportValue] of Object.entries(module)) {
583
+ if (isJayAction(exportValue)) {
584
+ registry.register(exportValue);
585
+ result.actionNames.push(exportValue.actionName);
586
+ result.actionCount++;
587
+ if (verbose) {
588
+ console.log(`[Actions] Registered: ${exportValue.actionName}`);
589
+ }
590
+ }
591
+ }
592
+ } catch (error) {
593
+ console.error(`[Actions] Failed to import ${filePath}:`, error);
594
+ }
595
+ }
596
+ return result;
597
+ }
598
+ async function findActionFiles(dir) {
599
+ const files = [];
600
+ const entries = await fs.promises.readdir(dir, { withFileTypes: true });
601
+ for (const entry of entries) {
602
+ const fullPath = path.join(dir, entry.name);
603
+ if (entry.isDirectory()) {
604
+ const subFiles = await findActionFiles(fullPath);
605
+ files.push(...subFiles);
606
+ } else if (entry.isFile() && entry.name.endsWith(".actions.ts")) {
607
+ files.push(fullPath);
608
+ }
609
+ }
610
+ return files;
611
+ }
612
+ async function discoverAllPluginActions(options) {
613
+ const { projectRoot, registry = actionRegistry, verbose = false, viteServer } = options;
614
+ const allActions = [];
615
+ const localPluginsPath = path.join(projectRoot, "src/plugins");
616
+ if (fs.existsSync(localPluginsPath)) {
617
+ const pluginDirs = await fs.promises.readdir(localPluginsPath, { withFileTypes: true });
618
+ for (const entry of pluginDirs) {
619
+ if (entry.isDirectory()) {
620
+ const pluginPath = path.join(localPluginsPath, entry.name);
621
+ const actions = await discoverPluginActions(
622
+ pluginPath,
623
+ projectRoot,
624
+ registry,
625
+ verbose,
626
+ viteServer
627
+ );
628
+ allActions.push(...actions);
629
+ }
630
+ }
631
+ }
632
+ const npmActions = await discoverNpmPluginActions(projectRoot, registry, verbose, viteServer);
633
+ allActions.push(...npmActions);
634
+ return allActions;
635
+ }
636
+ async function discoverNpmPluginActions(projectRoot, registry, verbose, viteServer) {
637
+ const allActions = [];
638
+ const packageJsonPath = path.join(projectRoot, "package.json");
639
+ if (!fs.existsSync(packageJsonPath)) {
640
+ return allActions;
641
+ }
642
+ try {
643
+ const packageJson = JSON.parse(await fs.promises.readFile(packageJsonPath, "utf-8"));
644
+ const dependencies = {
645
+ ...packageJson.dependencies,
646
+ ...packageJson.devDependencies
647
+ };
648
+ for (const packageName of Object.keys(dependencies)) {
649
+ try {
650
+ const pluginYamlPath = tryResolvePluginYaml(packageName, projectRoot);
651
+ if (!pluginYamlPath) {
652
+ continue;
653
+ }
654
+ const pluginDir = path.dirname(pluginYamlPath);
655
+ const pluginConfig = loadPluginManifest(pluginDir);
656
+ if (!pluginConfig || !pluginConfig.actions || !Array.isArray(pluginConfig.actions)) {
657
+ continue;
658
+ }
659
+ if (verbose) {
660
+ console.log(
661
+ `[Actions] NPM plugin "${packageName}" declares actions:`,
662
+ pluginConfig.actions
663
+ );
664
+ }
665
+ const actions = await registerNpmPluginActions(
666
+ packageName,
667
+ pluginConfig,
668
+ registry,
669
+ verbose,
670
+ viteServer
671
+ );
672
+ allActions.push(...actions);
673
+ } catch {
674
+ continue;
675
+ }
676
+ }
677
+ } catch (error) {
678
+ console.error("[Actions] Failed to read project package.json:", error);
679
+ }
680
+ return allActions;
681
+ }
682
+ function tryResolvePluginYaml(packageName, projectRoot) {
683
+ try {
684
+ return require$1.resolve(`${packageName}/plugin.yaml`, {
685
+ paths: [projectRoot]
686
+ });
687
+ } catch {
688
+ return null;
689
+ }
690
+ }
691
+ async function registerNpmPluginActions(packageName, pluginConfig, registry, verbose, viteServer) {
692
+ const registeredActions = [];
693
+ try {
694
+ let pluginModule;
695
+ if (viteServer) {
696
+ pluginModule = await viteServer.ssrLoadModule(packageName);
697
+ } else {
698
+ pluginModule = await import(packageName);
699
+ }
700
+ for (const actionName of pluginConfig.actions) {
701
+ const actionExport = pluginModule[actionName];
702
+ if (actionExport && isJayAction(actionExport)) {
703
+ registry.register(actionExport);
704
+ registeredActions.push(actionExport.actionName);
705
+ if (verbose) {
706
+ console.log(
707
+ `[Actions] Registered NPM plugin action: ${actionExport.actionName}`
708
+ );
709
+ }
710
+ } else {
711
+ console.warn(
712
+ `[Actions] NPM plugin "${packageName}" declares action "${actionName}" but it's not exported or not a JayAction`
713
+ );
714
+ }
715
+ }
716
+ } catch (importError) {
717
+ console.error(`[Actions] Failed to import NPM plugin "${packageName}":`, importError);
718
+ }
719
+ return registeredActions;
720
+ }
721
+ async function discoverPluginActions(pluginPath, projectRoot, registry = actionRegistry, verbose = false, viteServer) {
722
+ const pluginConfig = loadPluginManifest(pluginPath);
723
+ if (!pluginConfig) {
724
+ return [];
725
+ }
726
+ if (!pluginConfig.actions || !Array.isArray(pluginConfig.actions)) {
727
+ return [];
728
+ }
729
+ const registeredActions = [];
730
+ const pluginName = pluginConfig.name || path.basename(pluginPath);
731
+ if (verbose) {
732
+ console.log(`[Actions] Plugin "${pluginName}" declares actions:`, pluginConfig.actions);
733
+ }
734
+ let modulePath = pluginConfig.module ? path.join(pluginPath, pluginConfig.module) : path.join(pluginPath, "index.ts");
735
+ if (!fs.existsSync(modulePath)) {
736
+ const tsPath = modulePath + ".ts";
737
+ const jsPath = modulePath + ".js";
738
+ if (fs.existsSync(tsPath)) {
739
+ modulePath = tsPath;
740
+ } else if (fs.existsSync(jsPath)) {
741
+ modulePath = jsPath;
742
+ } else {
743
+ console.warn(`[Actions] Plugin "${pluginName}" module not found at ${modulePath}`);
744
+ return [];
745
+ }
746
+ }
747
+ try {
748
+ let pluginModule;
749
+ if (viteServer) {
750
+ pluginModule = await viteServer.ssrLoadModule(modulePath);
751
+ } else {
752
+ pluginModule = await import(modulePath);
753
+ }
754
+ for (const actionName of pluginConfig.actions) {
755
+ const actionExport = pluginModule[actionName];
756
+ if (actionExport && isJayAction(actionExport)) {
757
+ registry.register(actionExport);
758
+ registeredActions.push(actionExport.actionName);
759
+ if (verbose) {
760
+ console.log(
761
+ `[Actions] Registered plugin action: ${actionExport.actionName}`
762
+ );
763
+ }
764
+ } else {
765
+ console.warn(
766
+ `[Actions] Plugin "${pluginName}" declares action "${actionName}" but it's not exported or not a JayAction`
767
+ );
768
+ }
769
+ }
770
+ } catch (importError) {
771
+ console.error(`[Actions] Failed to import plugin module at ${modulePath}:`, importError);
772
+ }
773
+ return registeredActions;
774
+ }
775
+ const require2 = createRequire$1(import.meta.url);
776
+ async function discoverPluginsWithInit(options) {
777
+ const { projectRoot, verbose = false } = options;
778
+ const plugins = [];
779
+ const visitedPackages = /* @__PURE__ */ new Set();
780
+ const localPluginsPath = path.join(projectRoot, "src/plugins");
781
+ if (fs.existsSync(localPluginsPath)) {
782
+ try {
783
+ const entries = fs.readdirSync(localPluginsPath, { withFileTypes: true });
784
+ for (const entry of entries) {
785
+ if (!entry.isDirectory())
786
+ continue;
787
+ const pluginPath = path.join(localPluginsPath, entry.name);
788
+ const manifest = loadPluginManifest(pluginPath);
789
+ if (!manifest)
790
+ continue;
791
+ const initConfig = resolvePluginInit(pluginPath, manifest.init, true);
792
+ if (!initConfig)
793
+ continue;
794
+ const dependencies = await getPackageDependencies(pluginPath);
795
+ plugins.push({
796
+ name: manifest.name || entry.name,
797
+ pluginPath,
798
+ packageName: pluginPath,
799
+ // For local, use path
800
+ isLocal: true,
801
+ initModule: initConfig.module,
802
+ initExport: initConfig.export,
803
+ dependencies
804
+ });
805
+ visitedPackages.add(pluginPath);
806
+ if (verbose) {
807
+ console.log(
808
+ `[PluginInit] Found local plugin with init: ${manifest.name || entry.name}`
809
+ );
810
+ }
811
+ }
812
+ } catch (error) {
813
+ console.warn(`[PluginInit] Failed to scan local plugins: ${error}`);
814
+ }
815
+ }
816
+ const projectPackageJsonPath = path.join(projectRoot, "package.json");
817
+ if (fs.existsSync(projectPackageJsonPath)) {
818
+ try {
819
+ const projectPackageJson = JSON.parse(fs.readFileSync(projectPackageJsonPath, "utf-8"));
820
+ const initialDeps = Object.keys(projectPackageJson.dependencies || {});
821
+ const packagesToCheck = [...initialDeps];
822
+ while (packagesToCheck.length > 0) {
823
+ const depName = packagesToCheck.shift();
824
+ if (visitedPackages.has(depName))
825
+ continue;
826
+ visitedPackages.add(depName);
827
+ let pluginYamlPath;
828
+ try {
829
+ pluginYamlPath = require2.resolve(`${depName}/plugin.yaml`, {
830
+ paths: [projectRoot]
831
+ });
832
+ } catch {
833
+ continue;
834
+ }
835
+ const pluginPath = path.dirname(pluginYamlPath);
836
+ const manifest = loadPluginManifest(pluginPath);
837
+ if (!manifest)
838
+ continue;
839
+ const initConfig = resolvePluginInit(pluginPath, manifest.init, false);
840
+ if (!initConfig)
841
+ continue;
842
+ const dependencies = await getPackageDependencies(pluginPath);
843
+ plugins.push({
844
+ name: manifest.name || depName,
845
+ pluginPath,
846
+ packageName: depName,
847
+ isLocal: false,
848
+ initModule: initConfig.module,
849
+ initExport: initConfig.export,
850
+ dependencies
851
+ });
852
+ if (verbose) {
853
+ console.log(`[PluginInit] Found NPM plugin with init: ${depName}`);
854
+ }
855
+ for (const transitiveDep of dependencies) {
856
+ if (!visitedPackages.has(transitiveDep)) {
857
+ packagesToCheck.push(transitiveDep);
858
+ }
859
+ }
860
+ }
861
+ } catch (error) {
862
+ console.warn(`[PluginInit] Failed to scan NPM plugins: ${error}`);
863
+ }
864
+ }
865
+ return plugins;
866
+ }
867
+ function resolvePluginInit(pluginPath, initConfig, isLocal) {
868
+ const defaultExport = "init";
869
+ if (isLocal) {
870
+ const initPaths = [
871
+ path.join(pluginPath, "lib/init.ts"),
872
+ path.join(pluginPath, "lib/init.js"),
873
+ path.join(pluginPath, "init.ts"),
874
+ path.join(pluginPath, "init.js")
875
+ ];
876
+ for (const initPath of initPaths) {
877
+ if (fs.existsSync(initPath)) {
878
+ const relativePath = path.relative(pluginPath, initPath);
879
+ const modulePath = relativePath.replace(/\.(ts|js)$/, "");
880
+ const exportName = typeof initConfig === "string" ? initConfig : defaultExport;
881
+ return { module: modulePath, export: exportName };
882
+ }
883
+ }
884
+ return null;
885
+ }
886
+ const packageJsonPath = path.join(pluginPath, "package.json");
887
+ if (!fs.existsSync(packageJsonPath)) {
888
+ return null;
889
+ }
890
+ try {
891
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
892
+ const hasMain = packageJson.main || packageJson.exports;
893
+ if (!hasMain && !initConfig) {
894
+ return null;
895
+ }
896
+ const exportName = typeof initConfig === "string" ? initConfig : defaultExport;
897
+ return { module: "", export: exportName };
898
+ } catch {
899
+ return null;
900
+ }
901
+ }
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
+ function sortPluginsByDependencies(plugins) {
915
+ const pluginNames = new Set(plugins.map((p) => p.packageName));
916
+ const sorted = [];
917
+ const visited = /* @__PURE__ */ new Set();
918
+ const visiting = /* @__PURE__ */ new Set();
919
+ function visit(plugin) {
920
+ if (visited.has(plugin.packageName))
921
+ return;
922
+ if (visiting.has(plugin.packageName)) {
923
+ console.warn(`[PluginInit] Circular dependency detected for ${plugin.name}`);
924
+ return;
925
+ }
926
+ visiting.add(plugin.packageName);
927
+ for (const dep of plugin.dependencies) {
928
+ if (pluginNames.has(dep)) {
929
+ const depPlugin = plugins.find((p) => p.packageName === dep);
930
+ if (depPlugin) {
931
+ visit(depPlugin);
932
+ }
933
+ }
934
+ }
935
+ visiting.delete(plugin.packageName);
936
+ visited.add(plugin.packageName);
937
+ sorted.push(plugin);
938
+ }
939
+ for (const plugin of plugins) {
940
+ visit(plugin);
941
+ }
942
+ return sorted;
943
+ }
944
+ async function executePluginServerInits(plugins, viteServer, verbose = false) {
945
+ for (const plugin of plugins) {
946
+ try {
947
+ let modulePath;
948
+ if (plugin.isLocal) {
949
+ modulePath = path.join(plugin.pluginPath, plugin.initModule);
950
+ } else if (plugin.initModule) {
951
+ modulePath = `${plugin.packageName}/${plugin.initModule}`;
952
+ } else {
953
+ modulePath = plugin.packageName;
954
+ }
955
+ let pluginModule;
956
+ if (viteServer) {
957
+ pluginModule = await viteServer.ssrLoadModule(modulePath);
958
+ } else {
959
+ pluginModule = await import(modulePath);
960
+ }
961
+ const jayInit = pluginModule[plugin.initExport];
962
+ if (!jayInit || jayInit.__brand !== "JayInit") {
963
+ console.warn(
964
+ `[PluginInit] Plugin "${plugin.name}" init module doesn't export a valid JayInit at "${plugin.initExport}"`
965
+ );
966
+ continue;
967
+ }
968
+ if (typeof jayInit._serverInit === "function") {
969
+ if (verbose) {
970
+ console.log(`[DevServer] Running server init: ${plugin.name}`);
971
+ }
972
+ const clientData = await jayInit._serverInit();
973
+ if (clientData !== void 0 && clientData !== null) {
974
+ setClientInitData(plugin.name, clientData);
975
+ }
976
+ }
977
+ } catch (error) {
978
+ console.error(
979
+ `[PluginInit] Failed to execute server init for "${plugin.name}":`,
980
+ error
981
+ );
982
+ }
983
+ }
984
+ }
985
+ function preparePluginClientInits(plugins) {
986
+ return plugins.map((plugin) => {
987
+ let importPath;
988
+ if (plugin.isLocal) {
989
+ importPath = path.join(plugin.pluginPath, plugin.initModule);
990
+ } else if (plugin.initModule) {
991
+ importPath = `${plugin.packageName}/${plugin.initModule}`;
992
+ } else {
993
+ importPath = `${plugin.packageName}/client`;
994
+ }
995
+ return {
996
+ name: plugin.name,
997
+ importPath,
998
+ initExport: plugin.initExport
999
+ };
212
1000
  });
213
1001
  }
1002
+ function makeCacheKey(jayHtmlPath, params) {
1003
+ const sortedParams = Object.keys(params).sort().reduce(
1004
+ (acc, key) => {
1005
+ acc[key] = params[key];
1006
+ return acc;
1007
+ },
1008
+ {}
1009
+ );
1010
+ return `${jayHtmlPath}:${JSON.stringify(sortedParams)}`;
1011
+ }
1012
+ function hashParams(params) {
1013
+ const sortedParams = Object.keys(params).sort().reduce(
1014
+ (acc, key) => {
1015
+ acc[key] = params[key];
1016
+ return acc;
1017
+ },
1018
+ {}
1019
+ );
1020
+ const json = JSON.stringify(sortedParams);
1021
+ if (json === "{}")
1022
+ return "";
1023
+ return "_" + crypto.createHash("md5").update(json).digest("hex").substring(0, 8);
1024
+ }
1025
+ class SlowRenderCache {
1026
+ /**
1027
+ * @param cacheDir - Directory where pre-rendered jay-html files are stored
1028
+ * @param pagesRoot - Root directory of the pages (for relative path calculation)
1029
+ */
1030
+ constructor(cacheDir, pagesRoot) {
1031
+ __publicField(this, "cache", /* @__PURE__ */ new Map());
1032
+ __publicField(this, "pathToKeys", /* @__PURE__ */ new Map());
1033
+ __publicField(this, "cacheDir");
1034
+ __publicField(this, "pagesRoot");
1035
+ this.cacheDir = cacheDir;
1036
+ this.pagesRoot = pagesRoot;
1037
+ }
1038
+ /**
1039
+ * Get a cached pre-rendered jay-html entry
1040
+ */
1041
+ get(jayHtmlPath, params) {
1042
+ const key = makeCacheKey(jayHtmlPath, params);
1043
+ return this.cache.get(key);
1044
+ }
1045
+ /**
1046
+ * Store a pre-rendered jay-html entry in the cache.
1047
+ * Writes the pre-rendered content to disk and stores metadata in memory.
1048
+ */
1049
+ async set(jayHtmlPath, params, preRenderedJayHtml, slowViewState, carryForward) {
1050
+ const key = makeCacheKey(jayHtmlPath, params);
1051
+ const relativePath = path__default.relative(this.pagesRoot, jayHtmlPath);
1052
+ const dir = path__default.dirname(relativePath);
1053
+ const basename = path__default.basename(relativePath, ".jay-html");
1054
+ const paramsHash = hashParams(params);
1055
+ const cacheFileName = `${basename}${paramsHash}.jay-html`;
1056
+ const preRenderedPath = path__default.join(this.cacheDir, dir, cacheFileName);
1057
+ await fs$1.mkdir(path__default.dirname(preRenderedPath), { recursive: true });
1058
+ await fs$1.writeFile(preRenderedPath, preRenderedJayHtml, "utf-8");
1059
+ if (!this.pathToKeys.has(jayHtmlPath)) {
1060
+ this.pathToKeys.set(jayHtmlPath, /* @__PURE__ */ new Set());
1061
+ }
1062
+ this.pathToKeys.get(jayHtmlPath).add(key);
1063
+ const entry = {
1064
+ preRenderedPath,
1065
+ slowViewState,
1066
+ carryForward,
1067
+ createdAt: Date.now(),
1068
+ sourcePath: jayHtmlPath
1069
+ };
1070
+ this.cache.set(key, entry);
1071
+ return preRenderedPath;
1072
+ }
1073
+ /**
1074
+ * Check if a pre-rendered entry exists for the given path and params
1075
+ */
1076
+ has(jayHtmlPath, params) {
1077
+ const key = makeCacheKey(jayHtmlPath, params);
1078
+ return this.cache.has(key);
1079
+ }
1080
+ /**
1081
+ * Invalidate all cached entries for a given jay-html source path.
1082
+ * This is called when the source file changes.
1083
+ * Also deletes the cached files from disk.
1084
+ */
1085
+ async invalidate(jayHtmlPath) {
1086
+ const keys = this.pathToKeys.get(jayHtmlPath);
1087
+ if (keys) {
1088
+ for (const key of keys) {
1089
+ const entry = this.cache.get(key);
1090
+ if (entry) {
1091
+ try {
1092
+ await fs$1.unlink(entry.preRenderedPath);
1093
+ } catch {
1094
+ }
1095
+ }
1096
+ this.cache.delete(key);
1097
+ }
1098
+ this.pathToKeys.delete(jayHtmlPath);
1099
+ }
1100
+ }
1101
+ /**
1102
+ * Invalidate all entries that depend on a changed file.
1103
+ * The changedPath could be:
1104
+ * - A jay-html file itself
1105
+ * - A component file (page.ts)
1106
+ * - Any other dependency
1107
+ *
1108
+ * @param changedPath - Absolute path to the changed file
1109
+ * @param resolveDependencies - Optional function to resolve which jay-html files depend on the changed file
1110
+ */
1111
+ async invalidateByDependency(changedPath, resolveDependencies) {
1112
+ if (changedPath.endsWith(".jay-html")) {
1113
+ await this.invalidate(changedPath);
1114
+ return;
1115
+ }
1116
+ if (resolveDependencies) {
1117
+ const dependentPaths = resolveDependencies(changedPath);
1118
+ for (const depPath of dependentPaths) {
1119
+ await this.invalidate(depPath);
1120
+ }
1121
+ }
1122
+ }
1123
+ /**
1124
+ * Clear all cached entries and delete cached files from disk
1125
+ */
1126
+ async clear() {
1127
+ for (const entry of this.cache.values()) {
1128
+ try {
1129
+ await fs$1.unlink(entry.preRenderedPath);
1130
+ } catch {
1131
+ }
1132
+ }
1133
+ this.cache.clear();
1134
+ this.pathToKeys.clear();
1135
+ }
1136
+ /**
1137
+ * Get the number of cached entries
1138
+ */
1139
+ get size() {
1140
+ return this.cache.size;
1141
+ }
1142
+ /**
1143
+ * Get all cached jay-html paths (for debugging/monitoring)
1144
+ */
1145
+ getCachedPaths() {
1146
+ return Array.from(this.pathToKeys.keys());
1147
+ }
1148
+ }
214
1149
  export {
1150
+ ActionRegistry,
215
1151
  DevSlowlyChangingPhase,
1152
+ SlowRenderCache,
1153
+ actionRegistry,
1154
+ clearActionRegistry,
1155
+ clearClientInitData,
216
1156
  clearLifecycleCallbacks,
217
1157
  clearServiceRegistry,
1158
+ discoverAllPluginActions,
1159
+ discoverAndRegisterActions,
1160
+ discoverPluginActions,
1161
+ discoverPluginsWithInit,
1162
+ executeAction,
1163
+ executePluginServerInits,
218
1164
  generateClientScript,
1165
+ getActionCacheHeaders,
1166
+ getClientInitData,
1167
+ getClientInitDataForKey,
1168
+ getRegisteredAction,
1169
+ getRegisteredActionNames,
219
1170
  getService,
1171
+ hasAction,
220
1172
  hasService,
221
1173
  loadPageParts,
222
1174
  onInit,
223
1175
  onShutdown,
1176
+ preparePluginClientInits,
1177
+ registerAction,
224
1178
  registerService,
225
1179
  renderFastChangingData,
226
1180
  resolveServices,
1181
+ runAction,
227
1182
  runInitCallbacks,
228
1183
  runLoadParams,
229
1184
  runShutdownCallbacks,
230
- runSlowlyChangingRender
1185
+ runSlowlyChangingRender,
1186
+ setClientInitData,
1187
+ sortPluginsByDependencies
231
1188
  };