@jay-framework/stack-server-runtime 0.15.6 → 0.16.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.
- package/dist/index.d.ts +68 -13
- package/dist/index.js +249 -15
- package/package.json +13 -13
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { AnyJayStackComponentDefinition, PageProps, AnySlowlyRenderResult, UrlParams,
|
|
2
|
-
import { JayComponentCore } from '@jay-framework/component';
|
|
1
|
+
import { AnyJayStackComponentDefinition, PageProps, AnySlowlyRenderResult, UrlParams, AnyFastRenderResult, HttpMethod, CacheOptions, JayAction, JayActionDefinition, JayStreamAction, JayStreamActionDefinition, HeadTag, ServiceMarker } from '@jay-framework/fullstack-component';
|
|
3
2
|
import { ViteDevServer } from 'vite';
|
|
4
3
|
import { JayRoute } from '@jay-framework/stack-route-scanner';
|
|
5
4
|
import { WithValidations, JsonSchemaProperty, PluginManifest } from '@jay-framework/compiler-shared';
|
|
@@ -76,8 +75,11 @@ interface SlowlyChangingPhase {
|
|
|
76
75
|
declare class DevSlowlyChangingPhase implements SlowlyChangingPhase {
|
|
77
76
|
runSlowlyForPage(pageParams: UrlParams, pageProps: PageProps, parts: Array<DevServerPagePart>, discoveredInstances?: DiscoveredHeadlessInstance[], headlessInstanceComponents?: HeadlessInstanceComponent[], jayHtmlPath?: string): Promise<AnySlowlyRenderResult>;
|
|
78
77
|
}
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Run loadParams for all parts (page + keyed headless components).
|
|
80
|
+
* Yields param batches from each part that has loadParams.
|
|
81
|
+
*/
|
|
82
|
+
declare function runLoadParams(parts: DevServerPagePart[]): AsyncGenerator<Record<string, string>[]>;
|
|
81
83
|
|
|
82
84
|
/**
|
|
83
85
|
* Server-side slow render orchestration for headless component instances.
|
|
@@ -191,23 +193,43 @@ declare function resolveActionMetadataPath(actionPath: string, pluginDir: string
|
|
|
191
193
|
*/
|
|
192
194
|
|
|
193
195
|
/**
|
|
194
|
-
*
|
|
196
|
+
* Base fields shared by all registered action types.
|
|
195
197
|
*/
|
|
196
|
-
interface
|
|
198
|
+
interface RegisteredActionBase {
|
|
197
199
|
/** Unique action name */
|
|
198
200
|
actionName: string;
|
|
199
201
|
/** HTTP method */
|
|
200
202
|
method: HttpMethod;
|
|
201
|
-
/** Cache options (for GET requests) */
|
|
202
|
-
cacheOptions?: CacheOptions;
|
|
203
203
|
/** Service markers for dependency injection */
|
|
204
204
|
services: any[];
|
|
205
|
-
/** The handler function */
|
|
206
|
-
handler: (input: any, ...services: any[]) => Promise<any>;
|
|
207
205
|
/** Optional metadata from .jay-action file (description, input/output schemas).
|
|
208
206
|
* Actions with metadata are exposed to AI agents; those without are not. */
|
|
209
207
|
metadata?: ActionMetadata;
|
|
210
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* Registered request-response action entry.
|
|
211
|
+
* Uses `isStreaming` as a discriminator for the union.
|
|
212
|
+
*/
|
|
213
|
+
interface RegisteredAction extends RegisteredActionBase {
|
|
214
|
+
/** Discriminator: false or absent for regular actions */
|
|
215
|
+
isStreaming?: false;
|
|
216
|
+
/** Cache options (for GET requests) */
|
|
217
|
+
cacheOptions?: CacheOptions;
|
|
218
|
+
/** The handler function */
|
|
219
|
+
handler: (input: any, ...services: any[]) => Promise<any>;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Registered streaming action entry (DL#129).
|
|
223
|
+
*/
|
|
224
|
+
interface RegisteredStreamAction extends RegisteredActionBase {
|
|
225
|
+
method: 'POST';
|
|
226
|
+
/** Discriminator: true for streaming actions */
|
|
227
|
+
isStreaming: true;
|
|
228
|
+
/** The generator handler function */
|
|
229
|
+
handler: (input: any, ...services: any[]) => AsyncIterable<any>;
|
|
230
|
+
}
|
|
231
|
+
/** Union of all registered action types, discriminated by `isStreaming`. */
|
|
232
|
+
type RegisteredActionEntry = RegisteredAction | RegisteredStreamAction;
|
|
211
233
|
/**
|
|
212
234
|
* Result of executing an action.
|
|
213
235
|
*/
|
|
@@ -258,7 +280,7 @@ declare class ActionRegistry {
|
|
|
258
280
|
* @param actionName - The unique action name
|
|
259
281
|
* @returns The registered action or undefined
|
|
260
282
|
*/
|
|
261
|
-
get(actionName: string):
|
|
283
|
+
get(actionName: string): RegisteredActionEntry | undefined;
|
|
262
284
|
/**
|
|
263
285
|
* Checks if an action is registered.
|
|
264
286
|
*
|
|
@@ -310,6 +332,18 @@ declare class ActionRegistry {
|
|
|
310
332
|
* @returns Cache-Control header value or undefined
|
|
311
333
|
*/
|
|
312
334
|
getCacheHeaders(actionName: string): string | undefined;
|
|
335
|
+
/**
|
|
336
|
+
* Register a streaming action.
|
|
337
|
+
*/
|
|
338
|
+
registerStream<I, C, S extends any[]>(action: JayStreamAction<I, C> & JayStreamActionDefinition<I, C, S>): void;
|
|
339
|
+
/**
|
|
340
|
+
* Check if a registered action is a streaming action.
|
|
341
|
+
*/
|
|
342
|
+
isStreaming(actionName: string): boolean;
|
|
343
|
+
/**
|
|
344
|
+
* Execute a streaming action, returning an async iterable of chunks.
|
|
345
|
+
*/
|
|
346
|
+
executeStream(actionName: string, input: unknown): AsyncGenerator<any>;
|
|
313
347
|
}
|
|
314
348
|
/**
|
|
315
349
|
* Default action registry instance.
|
|
@@ -325,7 +359,7 @@ declare function registerAction<I, O, S extends any[]>(action: JayAction<I, O> &
|
|
|
325
359
|
* Retrieves a registered action by name from the default registry.
|
|
326
360
|
* @deprecated Use actionRegistry.get() instead
|
|
327
361
|
*/
|
|
328
|
-
declare function getRegisteredAction(actionName: string):
|
|
362
|
+
declare function getRegisteredAction(actionName: string): RegisteredActionEntry | undefined;
|
|
329
363
|
/**
|
|
330
364
|
* Checks if an action is registered in the default registry.
|
|
331
365
|
* @deprecated Use actionRegistry.has() instead
|
|
@@ -575,6 +609,8 @@ interface GenerateClientScriptOptions {
|
|
|
575
609
|
* so that AI/automation tools can see the complete page state.
|
|
576
610
|
*/
|
|
577
611
|
slowViewState?: object;
|
|
612
|
+
/** Route pattern (e.g., /products/kitan{/:category}) for freeze entries */
|
|
613
|
+
routePattern?: string;
|
|
578
614
|
}
|
|
579
615
|
/**
|
|
580
616
|
* Shared fragments generated by buildScriptFragments().
|
|
@@ -637,6 +673,18 @@ declare function generateSSRPageHtml(vite: ViteDevServer, jayHtmlContent: string
|
|
|
637
673
|
sourceDir?: string,
|
|
638
674
|
/** Head tags to inject into <head> during SSR (Design Log #127) */
|
|
639
675
|
headTags?: HeadTag[]): Promise<string>;
|
|
676
|
+
/**
|
|
677
|
+
* Generate a frozen page — pure SSR HTML with no client scripts (DL#127).
|
|
678
|
+
*
|
|
679
|
+
* Uses the same server element module as generateSSRPageHtml, but:
|
|
680
|
+
* - No hydration script
|
|
681
|
+
* - No Vite client
|
|
682
|
+
* - No component runtime
|
|
683
|
+
* - Just rendered HTML + CSS
|
|
684
|
+
*
|
|
685
|
+
* @param format - 'page' for full HTML document, 'fragment' for body-only (shadow DOM)
|
|
686
|
+
*/
|
|
687
|
+
declare function generateFrozenPageHtml(vite: ViteDevServer, jayHtmlContent: string, jayHtmlFilename: string, jayHtmlDir: string, viewState: object, buildFolder: string, projectRoot: string, routeDir: string, tsConfigFilePath?: string, sourceDir?: string, format?: 'page' | 'fragment', freezeName?: string): Promise<string>;
|
|
640
688
|
|
|
641
689
|
/**
|
|
642
690
|
* Service registry for Jay Stack server-side dependency injection.
|
|
@@ -935,6 +983,11 @@ interface ContextIndexEntry {
|
|
|
935
983
|
description?: string;
|
|
936
984
|
doc?: string;
|
|
937
985
|
}
|
|
986
|
+
/** Route entry in plugins-index.yaml (DL#130) */
|
|
987
|
+
interface RouteIndexEntry {
|
|
988
|
+
path: string;
|
|
989
|
+
description?: string;
|
|
990
|
+
}
|
|
938
991
|
/** Entry for plugins-index.yaml (Design Log #85) */
|
|
939
992
|
interface PluginsIndexEntry {
|
|
940
993
|
name: string;
|
|
@@ -946,6 +999,8 @@ interface PluginsIndexEntry {
|
|
|
946
999
|
services?: ServiceIndexEntry[];
|
|
947
1000
|
/** Client-side contexts provided by this plugin (DL#125) */
|
|
948
1001
|
contexts?: ContextIndexEntry[];
|
|
1002
|
+
/** Plugin-provided routes (DL#130) */
|
|
1003
|
+
routes?: RouteIndexEntry[];
|
|
949
1004
|
}
|
|
950
1005
|
interface PluginsIndex {
|
|
951
1006
|
plugins: PluginsIndexEntry[];
|
|
@@ -1202,4 +1257,4 @@ declare function mergeHeadTags(sources: HeadTag[][]): HeadTag[];
|
|
|
1202
1257
|
*/
|
|
1203
1258
|
declare function serializeHeadTags(tags: HeadTag[]): string;
|
|
1204
1259
|
|
|
1205
|
-
export { type ActionDiscoveryOptions, type ActionDiscoveryResult, type ActionErrorResponse, type ActionExecutionResult, type ActionIndexEntry, type ActionMetadata, ActionRegistry, type ActionSchema, type ContextIndexEntry, type DevServerPagePart, DevSlowlyChangingPhase, type GenerateClientScriptOptions, type HeadlessInstanceComponent, type InstancePhaseData, type InstanceSlowRenderResult, type LoadedPageParts, type MaterializeContractsOptions, type MaterializeResult, type PluginActionDiscoveryOptions, type PluginClientInitInfo, type PluginContractEntry, type PluginInitDiscoveryOptions, type PluginReferencesContext, type PluginReferencesHandler, type PluginReferencesResult, type PluginScanOptions, type PluginSetupContext, type PluginSetupHandler, type PluginSetupResult, type PluginWithInit, type PluginWithReferences, type PluginWithSetup, type PluginsIndex, type PluginsIndexEntry, type ProjectClientInitInfo, type RegisteredAction, type ScannedPlugin, type ScriptFragments, type ServiceIndexEntry, SlowRenderCache, type SlowRenderCacheEntry, type SlowlyChangingPhase, type ViteSSRLoader, actionRegistry, buildAutomationWrap, buildScriptFragments, clearActionRegistry, clearClientInitData, clearLifecycleCallbacks, clearServerElementCache, clearServiceRegistry, discoverAllPluginActions, discoverAndRegisterActions, discoverPluginActions, discoverPluginsWithInit, discoverPluginsWithReferences, discoverPluginsWithSetup, executeAction, executePluginReferences, executePluginServerInits, executePluginSetup, generateClientScript, generatePromiseReconstruction, generateSSRPageHtml, getActionCacheHeaders, getClientInitData, getClientInitDataForKey, getRegisteredAction, getRegisteredActionNames, getService, getServiceRegistry, hasAction, hasService, invalidateServerElementCache, listContracts, loadActionMetadata, loadPageParts, materializeContracts, mergeHeadTags, onInit, onShutdown, parseActionMetadata, preparePluginClientInits, registerAction, registerService, renderFastChangingData, resolveActionMetadataPath, resolveServices, resolveViewStatePromises, runInitCallbacks, runLoadParams, runShutdownCallbacks,
|
|
1260
|
+
export { type ActionDiscoveryOptions, type ActionDiscoveryResult, type ActionErrorResponse, type ActionExecutionResult, type ActionIndexEntry, type ActionMetadata, ActionRegistry, type ActionSchema, type ContextIndexEntry, type DevServerPagePart, DevSlowlyChangingPhase, type GenerateClientScriptOptions, type HeadlessInstanceComponent, type InstancePhaseData, type InstanceSlowRenderResult, type LoadedPageParts, type MaterializeContractsOptions, type MaterializeResult, type PluginActionDiscoveryOptions, type PluginClientInitInfo, type PluginContractEntry, type PluginInitDiscoveryOptions, type PluginReferencesContext, type PluginReferencesHandler, type PluginReferencesResult, type PluginScanOptions, type PluginSetupContext, type PluginSetupHandler, type PluginSetupResult, type PluginWithInit, type PluginWithReferences, type PluginWithSetup, type PluginsIndex, type PluginsIndexEntry, type ProjectClientInitInfo, type RegisteredAction, type RegisteredActionBase, type RegisteredActionEntry, type RegisteredStreamAction, type RouteIndexEntry, type ScannedPlugin, type ScriptFragments, type ServiceIndexEntry, SlowRenderCache, type SlowRenderCacheEntry, type SlowlyChangingPhase, type ViteSSRLoader, actionRegistry, buildAutomationWrap, buildScriptFragments, clearActionRegistry, clearClientInitData, clearLifecycleCallbacks, clearServerElementCache, clearServiceRegistry, discoverAllPluginActions, discoverAndRegisterActions, discoverPluginActions, discoverPluginsWithInit, discoverPluginsWithReferences, discoverPluginsWithSetup, executeAction, executePluginReferences, executePluginServerInits, executePluginSetup, generateClientScript, generateFrozenPageHtml, generatePromiseReconstruction, generateSSRPageHtml, getActionCacheHeaders, getClientInitData, getClientInitDataForKey, getRegisteredAction, getRegisteredActionNames, getService, getServiceRegistry, hasAction, hasService, invalidateServerElementCache, listContracts, loadActionMetadata, loadPageParts, materializeContracts, mergeHeadTags, onInit, onShutdown, parseActionMetadata, preparePluginClientInits, registerAction, registerService, renderFastChangingData, resolveActionMetadataPath, resolveServices, resolveViewStatePromises, runInitCallbacks, runLoadParams, runShutdownCallbacks, scanPlugins, serializeHeadTags, setClientInitData, slowRenderInstances, sortPluginsByDependencies, tagIdentityKey, validateForEachInstances };
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ var __publicField = (obj, key, value) => {
|
|
|
4
4
|
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
5
5
|
return value;
|
|
6
6
|
};
|
|
7
|
-
import { phaseOutput, isJayAction } from "@jay-framework/fullstack-component";
|
|
7
|
+
import { phaseOutput, isJayAction, isJayStreamAction } from "@jay-framework/fullstack-component";
|
|
8
8
|
import "prettier";
|
|
9
9
|
import "js-beautify";
|
|
10
10
|
import fs$1 from "fs";
|
|
@@ -178,10 +178,15 @@ class DevSlowlyChangingPhase {
|
|
|
178
178
|
return phaseOutput(slowlyViewState, carryForward);
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
|
-
async function runLoadParams(
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
181
|
+
async function* runLoadParams(parts) {
|
|
182
|
+
for (const part of parts) {
|
|
183
|
+
if (part.compDefinition.loadParams) {
|
|
184
|
+
const services = resolveServices(part.compDefinition.services);
|
|
185
|
+
for await (const batch of part.compDefinition.loadParams(services)) {
|
|
186
|
+
yield batch;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
185
190
|
}
|
|
186
191
|
var __defProp2 = Object.defineProperty;
|
|
187
192
|
var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -403,11 +408,7 @@ async function renderFastChangingData(pageParams, pageProps, carryForward, parts
|
|
|
403
408
|
metadata: contractInfo.metadata
|
|
404
409
|
}
|
|
405
410
|
};
|
|
406
|
-
const fastRenderedPart = await compDefinition.fastRender(
|
|
407
|
-
partProps,
|
|
408
|
-
partSlowlyCarryForward,
|
|
409
|
-
...services
|
|
410
|
-
);
|
|
411
|
+
const fastRenderedPart = compDefinition.slowlyRender ? await compDefinition.fastRender(partProps, partSlowlyCarryForward, ...services) : await compDefinition.fastRender(partProps, ...services);
|
|
411
412
|
if (fastRenderedPart.kind === "PhaseOutput") {
|
|
412
413
|
if (!key) {
|
|
413
414
|
fastViewState = { ...fastViewState, ...fastRenderedPart.rendered };
|
|
@@ -576,6 +577,80 @@ ${parts.map((part) => " " + part.clientPart).join(",\n")}
|
|
|
576
577
|
slowViewStateDecl
|
|
577
578
|
};
|
|
578
579
|
}
|
|
580
|
+
function buildFreezeScript(routePattern) {
|
|
581
|
+
const routePatternLiteral = routePattern ? `'${routePattern}'` : "undefined";
|
|
582
|
+
return `
|
|
583
|
+
// Page Freeze (DL#127, DL#128 iframe addendum)
|
|
584
|
+
// Sticky embed mode: URL param sets a session cookie so in-iframe navigation preserves it
|
|
585
|
+
if (new URLSearchParams(window.location.search).has('_jay_embed')) {
|
|
586
|
+
document.cookie = '_jay_embed=1;path=/;samesite=lax';
|
|
587
|
+
}
|
|
588
|
+
const __jayEmbedMode = document.cookie.split(';').some(c => c.trim().startsWith('_jay_embed='));
|
|
589
|
+
|
|
590
|
+
async function __jayDoFreeze() {
|
|
591
|
+
const automation = window.__jay?.automation;
|
|
592
|
+
if (!automation) return;
|
|
593
|
+
|
|
594
|
+
// Visual feedback: white flash
|
|
595
|
+
const flash = document.createElement('div');
|
|
596
|
+
flash.style.cssText = 'position:fixed;inset:0;background:white;z-index:999999;opacity:0.8;pointer-events:none;transition:opacity 0.3s';
|
|
597
|
+
document.body.appendChild(flash);
|
|
598
|
+
requestAnimationFrame(() => { flash.style.opacity = '0'; });
|
|
599
|
+
setTimeout(() => flash.remove(), 400);
|
|
600
|
+
|
|
601
|
+
// Audio feedback: camera shutter
|
|
602
|
+
try {
|
|
603
|
+
const ctx = new AudioContext();
|
|
604
|
+
const buf = ctx.createBuffer(1, ctx.sampleRate * 0.15, ctx.sampleRate);
|
|
605
|
+
const data = buf.getChannelData(0);
|
|
606
|
+
for (let i = 0; i < data.length; i++) {
|
|
607
|
+
data[i] = (Math.random() * 2 - 1) * Math.exp(-i / (ctx.sampleRate * 0.02));
|
|
608
|
+
}
|
|
609
|
+
const src = ctx.createBufferSource();
|
|
610
|
+
src.buffer = buf;
|
|
611
|
+
src.connect(ctx.destination);
|
|
612
|
+
src.start();
|
|
613
|
+
} catch {}
|
|
614
|
+
|
|
615
|
+
// Capture and save
|
|
616
|
+
try {
|
|
617
|
+
const state = automation.getPageState();
|
|
618
|
+
const route = window.location.pathname;
|
|
619
|
+
const routePattern = ${routePatternLiteral};
|
|
620
|
+
const resp = await fetch('/_jay/freeze', {
|
|
621
|
+
method: 'POST',
|
|
622
|
+
headers: { 'Content-Type': 'application/json' },
|
|
623
|
+
body: JSON.stringify({ route, routePattern, viewState: state.viewState }),
|
|
624
|
+
});
|
|
625
|
+
const { id } = await resp.json();
|
|
626
|
+
if (__jayEmbedMode) {
|
|
627
|
+
window.parent.postMessage({ type: 'jay:freeze', id, route }, '*');
|
|
628
|
+
} else {
|
|
629
|
+
window.open(route + '?_jay_freeze=' + id, '_blank');
|
|
630
|
+
}
|
|
631
|
+
} catch (err) {
|
|
632
|
+
console.error('[Freeze] Failed:', err);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (__jayEmbedMode) {
|
|
637
|
+
// Notify parent of the current route on load (DL#128 route addendum)
|
|
638
|
+
window.parent.postMessage({ type: 'jay:route', route: window.location.pathname, routePattern: ${routePatternLiteral} }, '*');
|
|
639
|
+
|
|
640
|
+
// Embed mode: parent triggers freeze via postMessage
|
|
641
|
+
window.addEventListener('message', (e) => {
|
|
642
|
+
if (e.data?.type === 'jay:requestFreeze') __jayDoFreeze();
|
|
643
|
+
});
|
|
644
|
+
} else {
|
|
645
|
+
// Standalone: Alt+S / Option+S keyboard shortcut
|
|
646
|
+
document.addEventListener('keydown', (e) => {
|
|
647
|
+
if (e.altKey && e.code === 'KeyS') {
|
|
648
|
+
e.preventDefault();
|
|
649
|
+
__jayDoFreeze();
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
}`;
|
|
653
|
+
}
|
|
579
654
|
function buildAutomationWrap(options, mode) {
|
|
580
655
|
const { enableAutomation = true, slowViewState } = options;
|
|
581
656
|
const hasSlowViewState = slowViewState && Object.keys(slowViewState).length > 0;
|
|
@@ -586,6 +661,7 @@ function buildAutomationWrap(options, mode) {
|
|
|
586
661
|
}
|
|
587
662
|
const appendLine = appendDom ? `
|
|
588
663
|
target.appendChild(wrapped.element.dom);` : "";
|
|
664
|
+
const freezeScript = buildFreezeScript(options.routePattern);
|
|
589
665
|
if (hasSlowViewState) {
|
|
590
666
|
return `
|
|
591
667
|
// Wrap with automation for dev tooling
|
|
@@ -595,7 +671,8 @@ function buildAutomationWrap(options, mode) {
|
|
|
595
671
|
registerGlobalContext(AUTOMATION_CONTEXT, wrapped.automation);
|
|
596
672
|
window.__jay = window.__jay || {};
|
|
597
673
|
window.__jay.automation = wrapped.automation;
|
|
598
|
-
window.dispatchEvent(new Event('jay:automation-ready'));${appendLine}
|
|
674
|
+
window.dispatchEvent(new Event('jay:automation-ready'));${appendLine}
|
|
675
|
+
${freezeScript}`;
|
|
599
676
|
}
|
|
600
677
|
return `
|
|
601
678
|
// Wrap with automation for dev tooling
|
|
@@ -603,7 +680,8 @@ function buildAutomationWrap(options, mode) {
|
|
|
603
680
|
registerGlobalContext(AUTOMATION_CONTEXT, wrapped.automation);
|
|
604
681
|
window.__jay = window.__jay || {};
|
|
605
682
|
window.__jay.automation = wrapped.automation;
|
|
606
|
-
window.dispatchEvent(new Event('jay:automation-ready'));${appendLine}
|
|
683
|
+
window.dispatchEvent(new Event('jay:automation-ready'));${appendLine}
|
|
684
|
+
${freezeScript}`;
|
|
607
685
|
}
|
|
608
686
|
async function resolveViewStatePromises(viewState) {
|
|
609
687
|
const entries = Object.entries(viewState);
|
|
@@ -830,6 +908,81 @@ ${titleHtml}${headExtras ? headExtras + "\n" : ""} </head>
|
|
|
830
908
|
</body>
|
|
831
909
|
</html>`;
|
|
832
910
|
}
|
|
911
|
+
async function generateFrozenPageHtml(vite, jayHtmlContent, jayHtmlFilename, jayHtmlDir, viewState, buildFolder, projectRoot, routeDir, tsConfigFilePath, sourceDir, format = "page", freezeName) {
|
|
912
|
+
const jayHtmlPath = path__default.join(jayHtmlDir, jayHtmlFilename);
|
|
913
|
+
let cached = serverModuleCache.get(jayHtmlPath);
|
|
914
|
+
if (!cached) {
|
|
915
|
+
cached = await compileAndLoadServerElement(
|
|
916
|
+
vite,
|
|
917
|
+
jayHtmlContent,
|
|
918
|
+
jayHtmlFilename,
|
|
919
|
+
jayHtmlDir,
|
|
920
|
+
buildFolder,
|
|
921
|
+
projectRoot,
|
|
922
|
+
routeDir,
|
|
923
|
+
tsConfigFilePath,
|
|
924
|
+
sourceDir
|
|
925
|
+
);
|
|
926
|
+
serverModuleCache.set(jayHtmlPath, cached);
|
|
927
|
+
}
|
|
928
|
+
const htmlChunks = [];
|
|
929
|
+
const ctx = {
|
|
930
|
+
write: (chunk) => {
|
|
931
|
+
htmlChunks.push(chunk);
|
|
932
|
+
},
|
|
933
|
+
onAsync: () => {
|
|
934
|
+
}
|
|
935
|
+
};
|
|
936
|
+
cached.renderToStream(viewState, ctx);
|
|
937
|
+
const ssrHtml = htmlChunks.join("");
|
|
938
|
+
if (format === "fragment") {
|
|
939
|
+
let inlineCss = "";
|
|
940
|
+
if (cached.cssHref) {
|
|
941
|
+
try {
|
|
942
|
+
const cssPath = cached.cssHref.replace(/^\/@fs/, "").replace(/\?.*$/, "");
|
|
943
|
+
const cssContent = await fs$2.readFile(cssPath, "utf-8");
|
|
944
|
+
inlineCss = `<style>${cssContent}</style>`;
|
|
945
|
+
} catch {
|
|
946
|
+
inlineCss = `<link rel="stylesheet" href="${cached.cssHref}" />`;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
return `${inlineCss}
|
|
950
|
+
${ssrHtml}`;
|
|
951
|
+
}
|
|
952
|
+
const headLinksHtml = cached.headLinks.map((link) => {
|
|
953
|
+
const attrs = Object.entries(link.attributes).map(([k, v]) => ` ${k}="${v}"`).join("");
|
|
954
|
+
return ` <link rel="${link.rel}" href="${link.href}"${attrs} />`;
|
|
955
|
+
}).join("\n");
|
|
956
|
+
const cssLink = cached.cssHref ? ` <link rel="stylesheet" href="${cached.cssHref}" />` : "";
|
|
957
|
+
const headExtras = [headLinksHtml, cssLink].filter((_) => _).join("\n");
|
|
958
|
+
const label = freezeName ? ` — ${freezeName}` : "";
|
|
959
|
+
return `<!doctype html>
|
|
960
|
+
<html lang="en">
|
|
961
|
+
<head>
|
|
962
|
+
<meta charset="UTF-8" />
|
|
963
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
964
|
+
<title>Frozen${label}</title>
|
|
965
|
+
${headExtras ? headExtras + "\n" : ""} <style>
|
|
966
|
+
body::before {
|
|
967
|
+
content: 'FROZEN${label ? `: ${freezeName}` : ""}';
|
|
968
|
+
position: fixed;
|
|
969
|
+
top: 0;
|
|
970
|
+
right: 0;
|
|
971
|
+
background: #1a1a2e;
|
|
972
|
+
color: #e0e0ff;
|
|
973
|
+
padding: 2px 10px;
|
|
974
|
+
font: 11px/1.6 system-ui;
|
|
975
|
+
z-index: 99999;
|
|
976
|
+
border-bottom-left-radius: 4px;
|
|
977
|
+
opacity: 0.8;
|
|
978
|
+
}
|
|
979
|
+
</style>
|
|
980
|
+
</head>
|
|
981
|
+
<body>
|
|
982
|
+
<div id="target">${ssrHtml}</div>
|
|
983
|
+
</body>
|
|
984
|
+
</html>`;
|
|
985
|
+
}
|
|
833
986
|
function rebaseRelativeImports(code, fromDir, toDir) {
|
|
834
987
|
return code.replace(/from "(\.\.\/[^"]+)"/g, (_match, relPath) => {
|
|
835
988
|
const absolutePath = path__default.resolve(fromDir, relPath);
|
|
@@ -1226,6 +1379,16 @@ class ActionRegistry {
|
|
|
1226
1379
|
}
|
|
1227
1380
|
};
|
|
1228
1381
|
}
|
|
1382
|
+
if (action.isStreaming) {
|
|
1383
|
+
return {
|
|
1384
|
+
success: false,
|
|
1385
|
+
error: {
|
|
1386
|
+
code: "STREAMING_ACTION",
|
|
1387
|
+
message: `Action '${actionName}' is a streaming action — use executeStream() instead`,
|
|
1388
|
+
isActionError: false
|
|
1389
|
+
}
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1229
1392
|
try {
|
|
1230
1393
|
const services = resolveServices(action.services);
|
|
1231
1394
|
const result = await action.handler(input, ...services);
|
|
@@ -1279,6 +1442,40 @@ class ActionRegistry {
|
|
|
1279
1442
|
}
|
|
1280
1443
|
return parts.length > 0 ? parts.join(", ") : void 0;
|
|
1281
1444
|
}
|
|
1445
|
+
// --- Streaming actions (DL#129) ---
|
|
1446
|
+
/**
|
|
1447
|
+
* Register a streaming action.
|
|
1448
|
+
*/
|
|
1449
|
+
registerStream(action) {
|
|
1450
|
+
const entry = {
|
|
1451
|
+
actionName: action.actionName,
|
|
1452
|
+
method: "POST",
|
|
1453
|
+
isStreaming: true,
|
|
1454
|
+
services: action.services,
|
|
1455
|
+
handler: action.handler
|
|
1456
|
+
};
|
|
1457
|
+
this.actions.set(action.actionName, entry);
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
* Check if a registered action is a streaming action.
|
|
1461
|
+
*/
|
|
1462
|
+
isStreaming(actionName) {
|
|
1463
|
+
const action = this.actions.get(actionName);
|
|
1464
|
+
return !!action?.isStreaming;
|
|
1465
|
+
}
|
|
1466
|
+
/**
|
|
1467
|
+
* Execute a streaming action, returning an async iterable of chunks.
|
|
1468
|
+
*/
|
|
1469
|
+
async *executeStream(actionName, input) {
|
|
1470
|
+
const action = this.actions.get(actionName);
|
|
1471
|
+
if (!action || !action.isStreaming) {
|
|
1472
|
+
throw new Error(`Streaming action '${actionName}' not found`);
|
|
1473
|
+
}
|
|
1474
|
+
const services = resolveServices(action.services);
|
|
1475
|
+
for await (const chunk of action.handler(input, ...services)) {
|
|
1476
|
+
yield chunk;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1282
1479
|
}
|
|
1283
1480
|
const actionRegistry = new ActionRegistry();
|
|
1284
1481
|
function registerAction(action) {
|
|
@@ -1402,6 +1599,15 @@ async function discoverAndRegisterActions(options) {
|
|
|
1402
1599
|
`[Actions] Registered: ${exportValue.actionName}`
|
|
1403
1600
|
);
|
|
1404
1601
|
}
|
|
1602
|
+
} else if (isJayStreamAction(exportValue)) {
|
|
1603
|
+
registry.registerStream(exportValue);
|
|
1604
|
+
result.actionNames.push(exportValue.actionName);
|
|
1605
|
+
result.actionCount++;
|
|
1606
|
+
if (verbose) {
|
|
1607
|
+
getLogger().info(
|
|
1608
|
+
`[Actions] Registered stream: ${exportValue.actionName}`
|
|
1609
|
+
);
|
|
1610
|
+
}
|
|
1405
1611
|
}
|
|
1406
1612
|
}
|
|
1407
1613
|
} catch (error) {
|
|
@@ -1558,6 +1764,13 @@ async function registerNpmPluginActions(packageName, pluginConfig, pluginDir, re
|
|
|
1558
1764
|
if (verbose) {
|
|
1559
1765
|
getLogger().info(`[Actions] Registered NPM plugin action: ${registeredName}`);
|
|
1560
1766
|
}
|
|
1767
|
+
} else if (actionExport && isJayStreamAction(actionExport)) {
|
|
1768
|
+
registry.registerStream(actionExport);
|
|
1769
|
+
const registeredName = actionExport.actionName;
|
|
1770
|
+
registeredActions.push(registeredName);
|
|
1771
|
+
if (verbose) {
|
|
1772
|
+
getLogger().info(`[Actions] Registered NPM plugin stream: ${registeredName}`);
|
|
1773
|
+
}
|
|
1561
1774
|
} else {
|
|
1562
1775
|
getLogger().warn(
|
|
1563
1776
|
`[Actions] NPM plugin "${packageName}" declares action "${actionName}" but it's not exported or not a JayAction`
|
|
@@ -1626,6 +1839,13 @@ async function discoverPluginActions(pluginPath, projectRoot, registry = actionR
|
|
|
1626
1839
|
if (verbose) {
|
|
1627
1840
|
getLogger().info(`[Actions] Registered plugin action: ${registeredName}`);
|
|
1628
1841
|
}
|
|
1842
|
+
} else if (actionExport && isJayStreamAction(actionExport)) {
|
|
1843
|
+
registry.registerStream(actionExport);
|
|
1844
|
+
const registeredName = actionExport.actionName;
|
|
1845
|
+
registeredActions.push(registeredName);
|
|
1846
|
+
if (verbose) {
|
|
1847
|
+
getLogger().info(`[Actions] Registered plugin stream: ${registeredName}`);
|
|
1848
|
+
}
|
|
1629
1849
|
} else {
|
|
1630
1850
|
getLogger().warn(
|
|
1631
1851
|
`[Actions] Plugin "${pluginName}" declares action "${actionName}" but it's not exported or not a JayAction`
|
|
@@ -2324,6 +2544,12 @@ async function materializeContracts(options, services = /* @__PURE__ */ new Map(
|
|
|
2324
2544
|
};
|
|
2325
2545
|
});
|
|
2326
2546
|
}
|
|
2547
|
+
if (manifest.routes?.length) {
|
|
2548
|
+
entry.routes = manifest.routes.map((r) => ({
|
|
2549
|
+
path: r.path,
|
|
2550
|
+
...r.description && { description: r.description }
|
|
2551
|
+
}));
|
|
2552
|
+
}
|
|
2327
2553
|
pluginsIndexMap.set(plugin.name, entry);
|
|
2328
2554
|
}
|
|
2329
2555
|
if (!dynamicOnly && manifest.contracts) {
|
|
@@ -2448,7 +2674,8 @@ async function materializeContracts(options, services = /* @__PURE__ */ new Map(
|
|
|
2448
2674
|
contracts: data.contracts,
|
|
2449
2675
|
...data.actions && data.actions.length > 0 && { actions: data.actions },
|
|
2450
2676
|
...data.services?.length && { services: data.services },
|
|
2451
|
-
...data.contexts?.length && { contexts: data.contexts }
|
|
2677
|
+
...data.contexts?.length && { contexts: data.contexts },
|
|
2678
|
+
...data.routes?.length && { routes: data.routes }
|
|
2452
2679
|
}))
|
|
2453
2680
|
};
|
|
2454
2681
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
@@ -2505,6 +2732,12 @@ async function listContracts(options) {
|
|
|
2505
2732
|
};
|
|
2506
2733
|
});
|
|
2507
2734
|
}
|
|
2735
|
+
if (manifest.routes?.length) {
|
|
2736
|
+
entry.routes = manifest.routes.map((r) => ({
|
|
2737
|
+
path: r.path,
|
|
2738
|
+
...r.description && { description: r.description }
|
|
2739
|
+
}));
|
|
2740
|
+
}
|
|
2508
2741
|
pluginsMap.set(plugin.name, entry);
|
|
2509
2742
|
}
|
|
2510
2743
|
if (!dynamicOnly && manifest.contracts) {
|
|
@@ -2551,7 +2784,8 @@ async function listContracts(options) {
|
|
|
2551
2784
|
path: data.path,
|
|
2552
2785
|
contracts: data.contracts,
|
|
2553
2786
|
...data.services?.length && { services: data.services },
|
|
2554
|
-
...data.contexts?.length && { contexts: data.contexts }
|
|
2787
|
+
...data.contexts?.length && { contexts: data.contexts },
|
|
2788
|
+
...data.routes?.length && { routes: data.routes }
|
|
2555
2789
|
}))
|
|
2556
2790
|
};
|
|
2557
2791
|
}
|
|
@@ -2705,6 +2939,7 @@ export {
|
|
|
2705
2939
|
executePluginServerInits,
|
|
2706
2940
|
executePluginSetup,
|
|
2707
2941
|
generateClientScript,
|
|
2942
|
+
generateFrozenPageHtml,
|
|
2708
2943
|
generatePromiseReconstruction,
|
|
2709
2944
|
generateSSRPageHtml,
|
|
2710
2945
|
getActionCacheHeaders,
|
|
@@ -2735,7 +2970,6 @@ export {
|
|
|
2735
2970
|
runInitCallbacks,
|
|
2736
2971
|
runLoadParams,
|
|
2737
2972
|
runShutdownCallbacks,
|
|
2738
|
-
runSlowlyChangingRender,
|
|
2739
2973
|
scanPlugins,
|
|
2740
2974
|
serializeHeadTags,
|
|
2741
2975
|
setClientInitData,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jay-framework/stack-server-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.mts",
|
|
@@ -26,21 +26,21 @@
|
|
|
26
26
|
"test:watch": "vitest"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@jay-framework/compiler-jay-html": "^0.
|
|
30
|
-
"@jay-framework/compiler-shared": "^0.
|
|
31
|
-
"@jay-framework/component": "^0.
|
|
32
|
-
"@jay-framework/fullstack-component": "^0.
|
|
33
|
-
"@jay-framework/logger": "^0.
|
|
34
|
-
"@jay-framework/runtime": "^0.
|
|
35
|
-
"@jay-framework/ssr-runtime": "^0.
|
|
36
|
-
"@jay-framework/stack-route-scanner": "^0.
|
|
37
|
-
"@jay-framework/view-state-merge": "^0.
|
|
29
|
+
"@jay-framework/compiler-jay-html": "^0.16.0",
|
|
30
|
+
"@jay-framework/compiler-shared": "^0.16.0",
|
|
31
|
+
"@jay-framework/component": "^0.16.0",
|
|
32
|
+
"@jay-framework/fullstack-component": "^0.16.0",
|
|
33
|
+
"@jay-framework/logger": "^0.16.0",
|
|
34
|
+
"@jay-framework/runtime": "^0.16.0",
|
|
35
|
+
"@jay-framework/ssr-runtime": "^0.16.0",
|
|
36
|
+
"@jay-framework/stack-route-scanner": "^0.16.0",
|
|
37
|
+
"@jay-framework/view-state-merge": "^0.16.0",
|
|
38
38
|
"yaml": "^2.3.4"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@jay-framework/dev-environment": "^0.
|
|
42
|
-
"@jay-framework/jay-cli": "^0.
|
|
43
|
-
"@jay-framework/stack-client-runtime": "^0.
|
|
41
|
+
"@jay-framework/dev-environment": "^0.16.0",
|
|
42
|
+
"@jay-framework/jay-cli": "^0.16.0",
|
|
43
|
+
"@jay-framework/stack-client-runtime": "^0.16.0",
|
|
44
44
|
"@types/express": "^5.0.2",
|
|
45
45
|
"@types/node": "^22.15.21",
|
|
46
46
|
"nodemon": "^3.0.3",
|