@jay-framework/fullstack-component 0.15.5 → 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 CHANGED
@@ -88,6 +88,19 @@ interface Redirect3xx {
88
88
  location: string;
89
89
  message?: string;
90
90
  }
91
+ /**
92
+ * A tag to inject into the HTML <head> during SSR (Design Log #127).
93
+ * Components return these from phaseOutput() to control page metadata (title, meta, link, etc.).
94
+ * Head tags are SSR-only — not hydrated on the client.
95
+ */
96
+ interface HeadTag {
97
+ /** Element name, e.g. 'title', 'meta', 'link' */
98
+ tag: string;
99
+ /** HTML attributes, e.g. { name: 'description', content: '...' } */
100
+ attrs?: Record<string, string>;
101
+ /** Text content, e.g. 'My Page Title' for <title> */
102
+ children?: string;
103
+ }
91
104
  /**
92
105
  * Successful output of a rendering phase.
93
106
  * Contains the rendered ViewState and data to carry forward to the next phase.
@@ -96,6 +109,8 @@ interface PhaseOutput<ViewState extends object, CarryForward = {}> {
96
109
  kind: 'PhaseOutput';
97
110
  rendered: ViewState;
98
111
  carryForward: CarryForward;
112
+ /** Tags to inject into <head> during SSR (Design Log #127). */
113
+ headTags?: HeadTag[];
99
114
  }
100
115
  /**
101
116
  * @deprecated Use PhaseOutput instead. PartialRender is kept for backwards compatibility.
@@ -181,8 +196,11 @@ declare function forbidden(message?: string, details?: Record<string, unknown>):
181
196
  declare function redirect3xx(status: number, location: string, message?: string): Redirect3xx;
182
197
  /**
183
198
  * Create a successful phase output with rendered ViewState and carry-forward data.
199
+ * Optionally include head tags to inject into <head> during SSR (Design Log #127).
184
200
  */
185
- declare function phaseOutput<ViewState extends object, CarryForward = {}>(rendered: ViewState, carryForward: CarryForward): PhaseOutput<ViewState, CarryForward>;
201
+ declare function phaseOutput<ViewState extends object, CarryForward = {}>(rendered: ViewState, carryForward: CarryForward, options?: {
202
+ headTags?: HeadTag[];
203
+ }): PhaseOutput<ViewState, CarryForward>;
186
204
  /**
187
205
  * @deprecated Use phaseOutput instead. Kept for backwards compatibility.
188
206
  */
@@ -410,6 +428,7 @@ declare class RenderPipeline<T, TargetVS extends object = object, TargetCF exten
410
428
  toPhaseOutput(fn: (value: T) => {
411
429
  viewState: TargetVS;
412
430
  carryForward: TargetCF;
431
+ headTags?: HeadTag[];
413
432
  }): Promise<RenderOutcome<TargetVS, TargetCF>>;
414
433
  /** Check if this pipeline is in a success state */
415
434
  isOk(): boolean;
@@ -551,6 +570,69 @@ type ActionOutput<T> = T extends JayAction<any, infer O> ? O : never;
551
570
  * Check if a value is a JayAction.
552
571
  */
553
572
  declare function isJayAction(value: unknown): value is JayAction<unknown, unknown>;
573
+ /**
574
+ * A callable streaming action that returns an async iterable of chunks.
575
+ * Server handler is an async generator; client receives chunks via NDJSON.
576
+ */
577
+ interface JayStreamAction<Input, Chunk> {
578
+ /** Call the action — returns async iterable of chunks */
579
+ (input: Input): AsyncIterable<Chunk>;
580
+ /** Unique action name for routing */
581
+ readonly actionName: string;
582
+ /** HTTP method (always POST for streaming) */
583
+ readonly method: 'POST';
584
+ /** Streaming flag */
585
+ readonly isStreaming: true;
586
+ /** Internal marker for type identification */
587
+ readonly _brand: 'JayStreamAction';
588
+ }
589
+ /**
590
+ * Internal definition for server-side registration of streaming actions.
591
+ */
592
+ interface JayStreamActionDefinition<Input, Chunk, Services extends any[]> {
593
+ actionName: string;
594
+ method: 'POST';
595
+ isStreaming: true;
596
+ services: ServiceMarkers<Services>;
597
+ handler: (input: Input, ...services: Services) => AsyncIterable<Chunk>;
598
+ }
599
+ /**
600
+ * Builder interface for streaming actions.
601
+ */
602
+ interface JayStreamBuilder<Services extends any[]> {
603
+ withServices<NewServices extends any[]>(...services: ServiceMarkers<NewServices>): JayStreamBuilder<NewServices>;
604
+ withHandler<I, C>(handler: (input: I, ...services: Services) => AsyncIterable<C>): JayStreamAction<I, C> & JayStreamActionDefinition<I, C, Services>;
605
+ }
606
+ /**
607
+ * Create a streaming action that yields chunks via an async generator.
608
+ * Use for paginated data, long-running operations, or any streaming response.
609
+ *
610
+ * @param name - Unique action name (e.g., 'routes.discoverParams')
611
+ *
612
+ * @example
613
+ * ```typescript
614
+ * export const discoverParams = makeJayStream('routes.discoverParams')
615
+ * .withServices(PRODUCTS_SERVICE)
616
+ * .withHandler(async function* (input: { route: string }, productsService) {
617
+ * let page = 1;
618
+ * while (true) {
619
+ * const products = await productsService.list({ page, pageSize: 100 });
620
+ * yield products.map(p => ({ slug: p.slug }));
621
+ * if (!products.hasMore) break;
622
+ * page++;
623
+ * }
624
+ * });
625
+ * ```
626
+ */
627
+ declare function makeJayStream(name: string): JayStreamBuilder<[]>;
628
+ /**
629
+ * Check if a value is a JayStreamAction.
630
+ */
631
+ declare function isJayStreamAction(value: unknown): value is JayStreamAction<unknown, unknown>;
632
+ /**
633
+ * Extract the chunk type from a JayStreamAction.
634
+ */
635
+ type StreamChunk<T> = T extends JayStreamAction<any, infer C> ? C : never;
554
636
 
555
637
  /**
556
638
  * Builder for plugin/project initialization with type-safe server-to-client data flow.
@@ -654,4 +736,4 @@ declare function makeJayInit(key?: string): JayInitBuilder<void>;
654
736
  */
655
737
  declare function isJayInit(obj: unknown): obj is JayInit<any>;
656
738
 
657
- export { ActionError, type ActionInput, type ActionOutput, type AnyFastRenderResult, type AnyJayStackComponentDefinition, type AnySlowlyRenderResult, type Builder, type CacheOptions, type ClientError4xx, type ContractGeneratorFunction, type DynamicContractGenerator, type DynamicContractProps, type FastRenderResult, type GeneratedContractYaml, type HttpMethod, type JayAction, type JayActionBuilder, type JayActionDefinition, type JayInit, type JayInitBuilder, type JayInitBuilderWithServer, type JayStackComponentDefinition, type LoadParams, type PageProps, type PartialRender, type PhaseOutput, type PipelineFactory, type Redirect3xx, type RenderFast, type RenderOutcome, RenderPipeline, type RenderSlowly, type RequestQuery, type ServerError5xx, type ServiceInstances, type ServiceMarker, type ServiceMarkers, type Signals, type SlowlyRenderResult, type UrlParams, badRequest, clientError4xx, createJayService, forbidden, isJayAction, isJayInit, makeContractGenerator, makeJayAction, makeJayInit, makeJayQuery, makeJayStackComponent, notFound, partialRender, phaseOutput, redirect3xx, serverError5xx, unauthorized };
739
+ export { ActionError, type ActionInput, type ActionOutput, type AnyFastRenderResult, type AnyJayStackComponentDefinition, type AnySlowlyRenderResult, type Builder, type CacheOptions, type ClientError4xx, type ContractGeneratorFunction, type DynamicContractGenerator, type DynamicContractProps, type FastRenderResult, type GeneratedContractYaml, type HeadTag, type HttpMethod, type JayAction, type JayActionBuilder, type JayActionDefinition, type JayInit, type JayInitBuilder, type JayInitBuilderWithServer, type JayStackComponentDefinition, type JayStreamAction, type JayStreamActionDefinition, type JayStreamBuilder, type LoadParams, type PageProps, type PartialRender, type PhaseOutput, type PipelineFactory, type Redirect3xx, type RenderFast, type RenderOutcome, RenderPipeline, type RenderSlowly, type RequestQuery, type ServerError5xx, type ServiceInstances, type ServiceMarker, type ServiceMarkers, type Signals, type SlowlyRenderResult, type StreamChunk, type UrlParams, badRequest, clientError4xx, createJayService, forbidden, isJayAction, isJayInit, isJayStreamAction, makeContractGenerator, makeJayAction, makeJayInit, makeJayQuery, makeJayStackComponent, makeJayStream, notFound, partialRender, phaseOutput, redirect3xx, serverError5xx, unauthorized };
package/dist/index.js CHANGED
@@ -40,8 +40,13 @@ function redirect3xx(status, location, message) {
40
40
  message
41
41
  };
42
42
  }
43
- function phaseOutput(rendered, carryForward) {
44
- return { kind: "PhaseOutput", rendered, carryForward };
43
+ function phaseOutput(rendered, carryForward, options) {
44
+ return {
45
+ kind: "PhaseOutput",
46
+ rendered,
47
+ carryForward,
48
+ ...options?.headTags && { headTags: options.headTags }
49
+ };
45
50
  }
46
51
  function partialRender(rendered, carryForward) {
47
52
  return phaseOutput(rendered, carryForward);
@@ -61,6 +66,7 @@ class BuilderImplementation {
61
66
  __publicField(this, "fastRender");
62
67
  __publicField(this, "comp");
63
68
  __publicField(this, "clientDefaults");
69
+ this.comp = () => ({ render: () => ({}) });
64
70
  }
65
71
  withProps() {
66
72
  return this;
@@ -331,8 +337,8 @@ class RenderPipeline {
331
337
  if (isErrorOutcome(resolvedValue)) {
332
338
  return resolvedValue;
333
339
  }
334
- const { viewState, carryForward } = fn(resolvedValue);
335
- return phaseOutput(viewState, carryForward);
340
+ const { viewState, carryForward, headTags } = fn(resolvedValue);
341
+ return phaseOutput(viewState, carryForward, headTags ? { headTags } : void 0);
336
342
  }
337
343
  // =========================================================================
338
344
  // Utility Methods
@@ -405,6 +411,42 @@ function makeJayQuery(name) {
405
411
  function isJayAction(value) {
406
412
  return typeof value === "function" && value._brand === "JayAction" && typeof value.actionName === "string";
407
413
  }
414
+ class JayStreamBuilderImpl {
415
+ constructor(_actionName) {
416
+ __publicField(this, "_services", []);
417
+ this._actionName = _actionName;
418
+ }
419
+ withServices(...services) {
420
+ this._services = services;
421
+ return this;
422
+ }
423
+ withHandler(handler) {
424
+ const actionName = this._actionName;
425
+ const serviceMarkers = this._services;
426
+ const action = Object.assign(
427
+ (input) => {
428
+ const resolver = globalThis.__JAY_SERVICE_RESOLVER__;
429
+ const resolvedServices = resolver ? resolver(serviceMarkers) : [];
430
+ return handler(input, ...resolvedServices);
431
+ },
432
+ {
433
+ actionName,
434
+ method: "POST",
435
+ isStreaming: true,
436
+ services: serviceMarkers,
437
+ handler,
438
+ _brand: "JayStreamAction"
439
+ }
440
+ );
441
+ return action;
442
+ }
443
+ }
444
+ function makeJayStream(name) {
445
+ return new JayStreamBuilderImpl(name);
446
+ }
447
+ function isJayStreamAction(value) {
448
+ return typeof value === "function" && value._brand === "JayStreamAction" && typeof value.actionName === "string";
449
+ }
408
450
  function makeJayInit(key) {
409
451
  const resolvedKey = key ?? "__JAY_INIT_KEY__";
410
452
  return {
@@ -449,11 +491,13 @@ export {
449
491
  forbidden,
450
492
  isJayAction,
451
493
  isJayInit,
494
+ isJayStreamAction,
452
495
  makeContractGenerator,
453
496
  makeJayAction,
454
497
  makeJayInit,
455
498
  makeJayQuery,
456
499
  makeJayStackComponent,
500
+ makeJayStream,
457
501
  notFound,
458
502
  partialRender,
459
503
  phaseOutput,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/fullstack-component",
3
- "version": "0.15.5",
3
+ "version": "0.16.0",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.js",
@@ -26,12 +26,12 @@
26
26
  "test:watch": "vitest"
27
27
  },
28
28
  "dependencies": {
29
- "@jay-framework/component": "^0.15.5",
30
- "@jay-framework/runtime": "^0.15.5"
29
+ "@jay-framework/component": "^0.16.0",
30
+ "@jay-framework/runtime": "^0.16.0"
31
31
  },
32
32
  "devDependencies": {
33
- "@jay-framework/dev-environment": "^0.15.5",
34
- "@jay-framework/jay-cli": "^0.15.5",
33
+ "@jay-framework/dev-environment": "^0.16.0",
34
+ "@jay-framework/jay-cli": "^0.16.0",
35
35
  "@types/express": "^5.0.2",
36
36
  "@types/node": "^22.15.21",
37
37
  "nodemon": "^3.0.3",