@jay-framework/fullstack-component 0.17.3 → 0.18.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
@@ -67,6 +67,13 @@ interface PageProps {
67
67
  interface RequestQuery {
68
68
  query: Record<string, string>;
69
69
  }
70
+ /**
71
+ * HTTP cookies parsed from the request Cookie header.
72
+ * Available in the fast phase only — not in the slow phase.
73
+ */
74
+ interface RequestCookies {
75
+ cookies: Record<string, string>;
76
+ }
70
77
  type UrlParams = Record<string, string>;
71
78
  interface ServerError5xx {
72
79
  kind: 'ServerError';
@@ -111,6 +118,8 @@ interface PhaseOutput<ViewState extends object, CarryForward = {}> {
111
118
  carryForward: CarryForward;
112
119
  /** Tags to inject into <head> during SSR (Design Log #127). */
113
120
  headTags?: HeadTag[];
121
+ /** HTTP response headers to set on the page response (Design Log #141). */
122
+ responseHeaders?: Record<string, string>;
114
123
  }
115
124
  /**
116
125
  * @deprecated Use PhaseOutput instead. PartialRender is kept for backwards compatibility.
@@ -126,7 +135,7 @@ type FastRenderResult<ViewState extends object, CarryForward = {}> = RenderOutco
126
135
  type AnyFastRenderResult = FastRenderResult<object, object>;
127
136
  type LoadParams<Services, Params extends UrlParams> = (contexts: Services) => AsyncIterable<Params[]>;
128
137
  type RenderSlowly<Services extends Array<object>, PropsT extends object, SlowViewState extends object, SlowlyCarryForward> = (props: PropsT, ...services: Services) => Promise<SlowlyRenderResult<SlowViewState, SlowlyCarryForward>>;
129
- type RenderFast<Services extends Array<object>, PropsT extends object, FastViewState extends object, FastCarryForward> = (props: PropsT & RequestQuery, ...services: Services) => Promise<FastRenderResult<FastViewState, FastCarryForward>>;
138
+ type RenderFast<Services extends Array<object>, PropsT extends object, FastViewState extends object, FastCarryForward> = (props: PropsT & RequestQuery & RequestCookies, ...services: Services) => Promise<FastRenderResult<FastViewState, FastCarryForward>>;
130
139
  interface JayStackComponentDefinition<Refs extends object, SlowVS extends object, FastVS extends object, InteractiveVS extends object, Services extends Array<any>, Contexts extends Array<any>, PropsT extends object, Params extends UrlParams, CompCore extends JayComponentCore<PropsT, InteractiveVS>> {
131
140
  services: ServiceMarkers<Services>;
132
141
  contexts: ContextMarkers<Contexts>;
@@ -202,6 +211,7 @@ declare function redirect3xx(status: number, location: string, message?: string)
202
211
  */
203
212
  declare function phaseOutput<ViewState extends object, CarryForward = {}>(rendered: ViewState, carryForward: CarryForward, options?: {
204
213
  headTags?: HeadTag[];
214
+ responseHeaders?: Record<string, string>;
205
215
  }): PhaseOutput<ViewState, CarryForward>;
206
216
  /**
207
217
  * @deprecated Use phaseOutput instead. Kept for backwards compatibility.
@@ -431,6 +441,7 @@ declare class RenderPipeline<T, TargetVS extends object = object, TargetCF exten
431
441
  viewState: TargetVS;
432
442
  carryForward: TargetCF;
433
443
  headTags?: HeadTag[];
444
+ responseHeaders?: Record<string, string>;
434
445
  }): Promise<RenderOutcome<TargetVS, TargetCF>>;
435
446
  /** Check if this pipeline is in a success state */
436
447
  isOk(): boolean;
@@ -466,15 +477,6 @@ interface JayFile {
466
477
  /** Absolute path to the temp file on disk (server-side only, always present in handlers) */
467
478
  path?: string;
468
479
  }
469
- /**
470
- * Options for file upload support.
471
- */
472
- interface FileUploadOptions {
473
- /** Maximum file size in bytes (default: 10MB) */
474
- maxFileSize?: number;
475
- /** Maximum number of files (default: 10) */
476
- maxFiles?: number;
477
- }
478
480
  /**
479
481
  * Supported HTTP methods for actions and queries.
480
482
  */
@@ -531,8 +533,6 @@ interface JayActionDefinition<Input, Output, Services extends any[]> {
531
533
  services: ServiceMarkers<Services>;
532
534
  /** Whether this action accepts file uploads (DL#131) */
533
535
  acceptsFiles?: boolean;
534
- /** File upload options (DL#131) */
535
- fileOptions?: FileUploadOptions;
536
536
  /** The handler function */
537
537
  handler: (input: Input, ...services: Services) => Promise<Output>;
538
538
  }
@@ -556,7 +556,7 @@ interface JayActionBuilder<Services extends any[], Input, Output, DefaultMethod
556
556
  * Mark this action as accepting file uploads (DL#131).
557
557
  * The handler will receive JayFile objects for file fields.
558
558
  */
559
- withFiles(options?: FileUploadOptions): JayActionBuilder<Services, Input, Output, DefaultMethod>;
559
+ withFiles(): JayActionBuilder<Services, Input, Output, DefaultMethod>;
560
560
  /**
561
561
  * Define the handler function. Input and output types are inferred from the handler signature.
562
562
  */
@@ -635,8 +635,6 @@ interface JayStreamActionDefinition<Input, Chunk, Services extends any[]> {
635
635
  services: ServiceMarkers<Services>;
636
636
  /** Whether this action accepts file uploads (DL#131) */
637
637
  acceptsFiles?: boolean;
638
- /** File upload options (DL#131) */
639
- fileOptions?: FileUploadOptions;
640
638
  handler: (input: Input, ...services: Services) => AsyncIterable<Chunk>;
641
639
  }
642
640
  /**
@@ -647,7 +645,7 @@ interface JayStreamBuilder<Services extends any[]> {
647
645
  /**
648
646
  * Mark this streaming action as accepting file uploads (DL#131).
649
647
  */
650
- withFiles(options?: FileUploadOptions): JayStreamBuilder<Services>;
648
+ withFiles(): JayStreamBuilder<Services>;
651
649
  withHandler<I, C>(handler: (input: I, ...services: Services) => AsyncIterable<C>): JayStreamAction<I, C> & JayStreamActionDefinition<I, C, Services>;
652
650
  }
653
651
  /**
@@ -681,6 +679,96 @@ declare function isJayStreamAction(value: unknown): value is JayStreamAction<unk
681
679
  */
682
680
  type StreamChunk<T> = T extends JayStreamAction<any, infer C> ? C : never;
683
681
 
682
+ interface WebhookEvent {
683
+ type: string;
684
+ payload: unknown;
685
+ headers: Record<string, string | undefined>;
686
+ }
687
+ interface InvalidateContract {
688
+ (contractName: string, params?: Record<string, string>): Promise<void>;
689
+ }
690
+ interface JayWebhook<Services extends any[] = any[]> {
691
+ readonly webhookName: string;
692
+ readonly services: ServiceMarkers<Services>;
693
+ readonly handler: (event: WebhookEvent, invalidate: InvalidateContract, ...services: Services) => Promise<void>;
694
+ readonly _brand: 'JayWebhook';
695
+ }
696
+ interface JayWebhookBuilder<Services extends any[]> {
697
+ withServices<NewServices extends any[]>(...services: ServiceMarkers<NewServices>): JayWebhookBuilder<NewServices>;
698
+ withHandler(handler: (event: WebhookEvent, invalidate: InvalidateContract, ...services: Services) => Promise<void>): JayWebhook<Services>;
699
+ }
700
+ /**
701
+ * Create a webhook handler for data change invalidation.
702
+ *
703
+ * @param name - Unique webhook name (e.g., 'wix-stores.product-change')
704
+ *
705
+ * @example
706
+ * ```typescript
707
+ * export const onProductChange = makeWebhook('wix-stores.product-change')
708
+ * .withServices(PRODUCTS_SERVICE)
709
+ * .withHandler(async (event, invalidate, productsService) => {
710
+ * const slug = await productsService.resolveSlug(event.payload.itemId);
711
+ * await invalidate('product-page', { slug });
712
+ * });
713
+ * ```
714
+ */
715
+ declare function makeWebhook(name: string): JayWebhookBuilder<[]>;
716
+ declare function isJayWebhook(value: unknown): value is JayWebhook;
717
+
718
+ interface ConsoleContext {
719
+ projectRoot: string;
720
+ publicFolder: string;
721
+ build: {
722
+ frontend: string;
723
+ backend: string;
724
+ };
725
+ verbose: boolean;
726
+ log: (message: string) => void;
727
+ warn: (message: string) => void;
728
+ error: (message: string) => void;
729
+ }
730
+ declare const CONSOLE_CONTEXT: ServiceMarker<ConsoleContext>;
731
+ interface JayCliCommand<Input> {
732
+ commandName: string;
733
+ services: ServiceMarkers<any[]>;
734
+ handler: (input: Input, ...services: any[]) => Promise<{
735
+ success: boolean;
736
+ }>;
737
+ _brand: 'JayCliCommand';
738
+ }
739
+ interface JayCliCommandDefinition<Input, Services extends any[]> {
740
+ commandName: string;
741
+ services: ServiceMarkers<Services>;
742
+ handler: (input: Input, ...services: Services) => Promise<{
743
+ success: boolean;
744
+ }>;
745
+ }
746
+ declare function isJayCliCommand(value: unknown): value is JayCliCommand<unknown>;
747
+ interface JayCliCommandBuilder<Services extends any[]> {
748
+ withServices<NewServices extends any[]>(...services: ServiceMarkers<NewServices>): JayCliCommandBuilder<NewServices>;
749
+ withHandler<I>(handler: (input: I, ...services: Services) => Promise<{
750
+ success: boolean;
751
+ }>): JayCliCommand<I> & JayCliCommandDefinition<I, Services>;
752
+ }
753
+ /**
754
+ * Create a CLI command that can be run via `jay-stack run <plugin>/<command>`.
755
+ * Use for admin/batch operations: media upload, deployment, data sync, etc.
756
+ *
757
+ * @param name - Command name (e.g., 'upload-public')
758
+ *
759
+ * @example
760
+ * ```typescript
761
+ * export const uploadPublic = makeCliCommand('upload-public')
762
+ * .withServices(MEDIA_SERVICE, CONSOLE_CONTEXT)
763
+ * .withHandler(async (input: { folder?: string; dryRun?: boolean }, mediaService, console) => {
764
+ * console.log('Uploading files...');
765
+ * // ...
766
+ * return { success: true };
767
+ * });
768
+ * ```
769
+ */
770
+ declare function makeCliCommand(name: string): JayCliCommandBuilder<[]>;
771
+
684
772
  /**
685
773
  * Builder for plugin/project initialization with type-safe server-to-client data flow.
686
774
  *
@@ -783,4 +871,4 @@ declare function makeJayInit(key?: string): JayInitBuilder<void>;
783
871
  */
784
872
  declare function isJayInit(obj: unknown): obj is JayInit<any>;
785
873
 
786
- 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 FileUploadOptions, type GeneratedContractYaml, type HeadTag, type HttpMethod, type JayAction, type JayActionBuilder, type JayActionDefinition, type JayFile, 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 };
874
+ export { ActionError, type ActionInput, type ActionOutput, type AnyFastRenderResult, type AnyJayStackComponentDefinition, type AnySlowlyRenderResult, type Builder, CONSOLE_CONTEXT, type CacheOptions, type ClientError4xx, type ConsoleContext, type ContractGeneratorFunction, type DynamicContractGenerator, type DynamicContractProps, type FastRenderResult, type GeneratedContractYaml, type HeadTag, type HttpMethod, type InvalidateContract, type JayAction, type JayActionBuilder, type JayActionDefinition, type JayCliCommand, type JayCliCommandBuilder, type JayCliCommandDefinition, type JayFile, type JayInit, type JayInitBuilder, type JayInitBuilderWithServer, type JayStackComponentDefinition, type JayStreamAction, type JayStreamActionDefinition, type JayStreamBuilder, type JayWebhook, type JayWebhookBuilder, type LoadParams, type PageProps, type PartialRender, type PhaseOutput, type PipelineFactory, type Redirect3xx, type RenderFast, type RenderOutcome, RenderPipeline, type RenderSlowly, type RequestCookies, type RequestQuery, type ServerError5xx, type ServiceInstances, type ServiceMarker, type ServiceMarkers, type Signals, type SlowlyRenderResult, type StreamChunk, type UrlParams, type WebhookEvent, badRequest, clientError4xx, createJayService, forbidden, isJayAction, isJayCliCommand, isJayInit, isJayStreamAction, isJayWebhook, makeCliCommand, makeContractGenerator, makeJayAction, makeJayInit, makeJayQuery, makeJayStackComponent, makeJayStream, makeWebhook, notFound, partialRender, phaseOutput, redirect3xx, serverError5xx, unauthorized };
package/dist/index.js CHANGED
@@ -45,7 +45,8 @@ function phaseOutput(rendered, carryForward, options) {
45
45
  kind: "PhaseOutput",
46
46
  rendered,
47
47
  carryForward,
48
- ...options?.headTags && { headTags: options.headTags }
48
+ ...options?.headTags && { headTags: options.headTags },
49
+ ...options?.responseHeaders && { responseHeaders: options.responseHeaders }
49
50
  };
50
51
  }
51
52
  function partialRender(rendered, carryForward) {
@@ -340,8 +341,9 @@ class RenderPipeline {
340
341
  if (isErrorOutcome(resolvedValue)) {
341
342
  return resolvedValue;
342
343
  }
343
- const { viewState, carryForward, headTags } = fn(resolvedValue);
344
- return phaseOutput(viewState, carryForward, headTags ? { headTags } : void 0);
344
+ const { viewState, carryForward, headTags, responseHeaders } = fn(resolvedValue);
345
+ const options = headTags || responseHeaders ? { headTags, responseHeaders } : void 0;
346
+ return phaseOutput(viewState, carryForward, options);
345
347
  }
346
348
  // =========================================================================
347
349
  // Utility Methods
@@ -368,7 +370,6 @@ class JayActionBuilderImpl {
368
370
  __publicField(this, "_method");
369
371
  __publicField(this, "_cacheOptions");
370
372
  __publicField(this, "_acceptsFiles", false);
371
- __publicField(this, "_fileOptions");
372
373
  this._actionName = _actionName;
373
374
  this._method = defaultMethod;
374
375
  }
@@ -384,9 +385,8 @@ class JayActionBuilderImpl {
384
385
  this._cacheOptions = options ?? { maxAge: 60 };
385
386
  return this;
386
387
  }
387
- withFiles(options) {
388
+ withFiles() {
388
389
  this._acceptsFiles = true;
389
- this._fileOptions = options;
390
390
  return this;
391
391
  }
392
392
  withHandler(handler) {
@@ -395,7 +395,6 @@ class JayActionBuilderImpl {
395
395
  const cacheOptions = this._cacheOptions;
396
396
  const serviceMarkers = this._services;
397
397
  const acceptsFiles = this._acceptsFiles;
398
- const fileOptions = this._fileOptions;
399
398
  const action = Object.assign(
400
399
  (input) => {
401
400
  const resolver = globalThis.__JAY_SERVICE_RESOLVER__;
@@ -409,8 +408,7 @@ class JayActionBuilderImpl {
409
408
  services: serviceMarkers,
410
409
  handler,
411
410
  _brand: "JayAction",
412
- ...acceptsFiles && { acceptsFiles: true },
413
- ...fileOptions && { fileOptions }
411
+ ...acceptsFiles && { acceptsFiles: true }
414
412
  }
415
413
  );
416
414
  return action;
@@ -429,23 +427,20 @@ class JayStreamBuilderImpl {
429
427
  constructor(_actionName) {
430
428
  __publicField(this, "_services", []);
431
429
  __publicField(this, "_acceptsFiles", false);
432
- __publicField(this, "_fileOptions");
433
430
  this._actionName = _actionName;
434
431
  }
435
432
  withServices(...services) {
436
433
  this._services = services;
437
434
  return this;
438
435
  }
439
- withFiles(options) {
436
+ withFiles() {
440
437
  this._acceptsFiles = true;
441
- this._fileOptions = options;
442
438
  return this;
443
439
  }
444
440
  withHandler(handler) {
445
441
  const actionName = this._actionName;
446
442
  const serviceMarkers = this._services;
447
443
  const acceptsFiles = this._acceptsFiles;
448
- const fileOptions = this._fileOptions;
449
444
  const action = Object.assign(
450
445
  (input) => {
451
446
  const resolver = globalThis.__JAY_SERVICE_RESOLVER__;
@@ -459,8 +454,7 @@ class JayStreamBuilderImpl {
459
454
  services: serviceMarkers,
460
455
  handler,
461
456
  _brand: "JayStreamAction",
462
- ...acceptsFiles && { acceptsFiles: true },
463
- ...fileOptions && { fileOptions }
457
+ ...acceptsFiles && { acceptsFiles: true }
464
458
  }
465
459
  );
466
460
  return action;
@@ -472,6 +466,55 @@ function makeJayStream(name) {
472
466
  function isJayStreamAction(value) {
473
467
  return typeof value === "function" && value._brand === "JayStreamAction" && typeof value.actionName === "string";
474
468
  }
469
+ class JayWebhookBuilderImpl {
470
+ constructor(_webhookName) {
471
+ __publicField(this, "_services", []);
472
+ this._webhookName = _webhookName;
473
+ }
474
+ withServices(...services) {
475
+ this._services = services;
476
+ return this;
477
+ }
478
+ withHandler(handler) {
479
+ return {
480
+ webhookName: this._webhookName,
481
+ services: this._services,
482
+ handler,
483
+ _brand: "JayWebhook"
484
+ };
485
+ }
486
+ }
487
+ function makeWebhook(name) {
488
+ return new JayWebhookBuilderImpl(name);
489
+ }
490
+ function isJayWebhook(value) {
491
+ return typeof value === "object" && value !== null && value._brand === "JayWebhook" && typeof value.webhookName === "string";
492
+ }
493
+ const CONSOLE_CONTEXT = createJayService("ConsoleContext");
494
+ function isJayCliCommand(value) {
495
+ return typeof value === "object" && value !== null && "_brand" in value && value._brand === "JayCliCommand";
496
+ }
497
+ class JayCliCommandBuilderImpl {
498
+ constructor(_commandName) {
499
+ __publicField(this, "_services", []);
500
+ this._commandName = _commandName;
501
+ }
502
+ withServices(...services) {
503
+ this._services = services;
504
+ return this;
505
+ }
506
+ withHandler(handler) {
507
+ return {
508
+ commandName: this._commandName,
509
+ services: this._services,
510
+ handler,
511
+ _brand: "JayCliCommand"
512
+ };
513
+ }
514
+ }
515
+ function makeCliCommand(name) {
516
+ return new JayCliCommandBuilderImpl(name);
517
+ }
475
518
  function makeJayInit(key) {
476
519
  const resolvedKey = key ?? "__JAY_INIT_KEY__";
477
520
  return {
@@ -509,20 +552,25 @@ function isJayInit(obj) {
509
552
  }
510
553
  export {
511
554
  ActionError,
555
+ CONSOLE_CONTEXT,
512
556
  RenderPipeline,
513
557
  badRequest,
514
558
  clientError4xx,
515
559
  createJayService,
516
560
  forbidden,
517
561
  isJayAction,
562
+ isJayCliCommand,
518
563
  isJayInit,
519
564
  isJayStreamAction,
565
+ isJayWebhook,
566
+ makeCliCommand,
520
567
  makeContractGenerator,
521
568
  makeJayAction,
522
569
  makeJayInit,
523
570
  makeJayQuery,
524
571
  makeJayStackComponent,
525
572
  makeJayStream,
573
+ makeWebhook,
526
574
  notFound,
527
575
  partialRender,
528
576
  phaseOutput,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/fullstack-component",
3
- "version": "0.17.3",
3
+ "version": "0.18.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.17.3",
30
- "@jay-framework/runtime": "^0.17.3"
29
+ "@jay-framework/component": "^0.18.0",
30
+ "@jay-framework/runtime": "^0.18.0"
31
31
  },
32
32
  "devDependencies": {
33
- "@jay-framework/dev-environment": "^0.17.3",
34
- "@jay-framework/jay-cli": "^0.17.3",
33
+ "@jay-framework/dev-environment": "^0.18.0",
34
+ "@jay-framework/jay-cli": "^0.18.0",
35
35
  "@types/express": "^5.0.2",
36
36
  "@types/node": "^22.15.21",
37
37
  "nodemon": "^3.0.3",