@jay-framework/fullstack-component 0.15.6 → 0.16.1

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
@@ -445,6 +445,34 @@ type ServiceResolver = (markers: any[]) => any[];
445
445
  declare global {
446
446
  var __JAY_SERVICE_RESOLVER__: ServiceResolver | undefined;
447
447
  }
448
+ /**
449
+ * Represents a file in a Jay action.
450
+ *
451
+ * On the **client**, browser `File` and `Blob` objects are assignable to this type
452
+ * (they have `name`, `type`, `size`). The framework converts them to FormData automatically.
453
+ *
454
+ * On the **server**, the framework populates `path` with a temp file location.
455
+ * The temp file is cleaned up after the handler returns.
456
+ */
457
+ interface JayFile {
458
+ /** Original filename */
459
+ name: string;
460
+ /** MIME type */
461
+ type: string;
462
+ /** File size in bytes */
463
+ size: number;
464
+ /** Absolute path to the temp file on disk (server-side only, always present in handlers) */
465
+ path?: string;
466
+ }
467
+ /**
468
+ * Options for file upload support.
469
+ */
470
+ interface FileUploadOptions {
471
+ /** Maximum file size in bytes (default: 10MB) */
472
+ maxFileSize?: number;
473
+ /** Maximum number of files (default: 10) */
474
+ maxFiles?: number;
475
+ }
448
476
  /**
449
477
  * Supported HTTP methods for actions and queries.
450
478
  */
@@ -499,6 +527,10 @@ interface JayActionDefinition<Input, Output, Services extends any[]> {
499
527
  cacheOptions?: CacheOptions;
500
528
  /** Service markers for dependency injection */
501
529
  services: ServiceMarkers<Services>;
530
+ /** Whether this action accepts file uploads (DL#131) */
531
+ acceptsFiles?: boolean;
532
+ /** File upload options (DL#131) */
533
+ fileOptions?: FileUploadOptions;
502
534
  /** The handler function */
503
535
  handler: (input: Input, ...services: Services) => Promise<Output>;
504
536
  }
@@ -518,6 +550,11 @@ interface JayActionBuilder<Services extends any[], Input, Output, DefaultMethod
518
550
  * Enable caching (typically for GET requests).
519
551
  */
520
552
  withCaching(options?: CacheOptions): JayActionBuilder<Services, Input, Output, DefaultMethod>;
553
+ /**
554
+ * Mark this action as accepting file uploads (DL#131).
555
+ * The handler will receive JayFile objects for file fields.
556
+ */
557
+ withFiles(options?: FileUploadOptions): JayActionBuilder<Services, Input, Output, DefaultMethod>;
521
558
  /**
522
559
  * Define the handler function. Input and output types are inferred from the handler signature.
523
560
  */
@@ -570,6 +607,77 @@ type ActionOutput<T> = T extends JayAction<any, infer O> ? O : never;
570
607
  * Check if a value is a JayAction.
571
608
  */
572
609
  declare function isJayAction(value: unknown): value is JayAction<unknown, unknown>;
610
+ /**
611
+ * A callable streaming action that returns an async iterable of chunks.
612
+ * Server handler is an async generator; client receives chunks via NDJSON.
613
+ */
614
+ interface JayStreamAction<Input, Chunk> {
615
+ /** Call the action — returns async iterable of chunks */
616
+ (input: Input): AsyncIterable<Chunk>;
617
+ /** Unique action name for routing */
618
+ readonly actionName: string;
619
+ /** HTTP method (always POST for streaming) */
620
+ readonly method: 'POST';
621
+ /** Streaming flag */
622
+ readonly isStreaming: true;
623
+ /** Internal marker for type identification */
624
+ readonly _brand: 'JayStreamAction';
625
+ }
626
+ /**
627
+ * Internal definition for server-side registration of streaming actions.
628
+ */
629
+ interface JayStreamActionDefinition<Input, Chunk, Services extends any[]> {
630
+ actionName: string;
631
+ method: 'POST';
632
+ isStreaming: true;
633
+ services: ServiceMarkers<Services>;
634
+ /** Whether this action accepts file uploads (DL#131) */
635
+ acceptsFiles?: boolean;
636
+ /** File upload options (DL#131) */
637
+ fileOptions?: FileUploadOptions;
638
+ handler: (input: Input, ...services: Services) => AsyncIterable<Chunk>;
639
+ }
640
+ /**
641
+ * Builder interface for streaming actions.
642
+ */
643
+ interface JayStreamBuilder<Services extends any[]> {
644
+ withServices<NewServices extends any[]>(...services: ServiceMarkers<NewServices>): JayStreamBuilder<NewServices>;
645
+ /**
646
+ * Mark this streaming action as accepting file uploads (DL#131).
647
+ */
648
+ withFiles(options?: FileUploadOptions): JayStreamBuilder<Services>;
649
+ withHandler<I, C>(handler: (input: I, ...services: Services) => AsyncIterable<C>): JayStreamAction<I, C> & JayStreamActionDefinition<I, C, Services>;
650
+ }
651
+ /**
652
+ * Create a streaming action that yields chunks via an async generator.
653
+ * Use for paginated data, long-running operations, or any streaming response.
654
+ *
655
+ * @param name - Unique action name (e.g., 'routes.discoverParams')
656
+ *
657
+ * @example
658
+ * ```typescript
659
+ * export const discoverParams = makeJayStream('routes.discoverParams')
660
+ * .withServices(PRODUCTS_SERVICE)
661
+ * .withHandler(async function* (input: { route: string }, productsService) {
662
+ * let page = 1;
663
+ * while (true) {
664
+ * const products = await productsService.list({ page, pageSize: 100 });
665
+ * yield products.map(p => ({ slug: p.slug }));
666
+ * if (!products.hasMore) break;
667
+ * page++;
668
+ * }
669
+ * });
670
+ * ```
671
+ */
672
+ declare function makeJayStream(name: string): JayStreamBuilder<[]>;
673
+ /**
674
+ * Check if a value is a JayStreamAction.
675
+ */
676
+ declare function isJayStreamAction(value: unknown): value is JayStreamAction<unknown, unknown>;
677
+ /**
678
+ * Extract the chunk type from a JayStreamAction.
679
+ */
680
+ type StreamChunk<T> = T extends JayStreamAction<any, infer C> ? C : never;
573
681
 
574
682
  /**
575
683
  * Builder for plugin/project initialization with type-safe server-to-client data flow.
@@ -673,4 +781,4 @@ declare function makeJayInit(key?: string): JayInitBuilder<void>;
673
781
  */
674
782
  declare function isJayInit(obj: unknown): obj is JayInit<any>;
675
783
 
676
- 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 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 };
784
+ 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 };
package/dist/index.js CHANGED
@@ -364,6 +364,8 @@ class JayActionBuilderImpl {
364
364
  __publicField(this, "_services", []);
365
365
  __publicField(this, "_method");
366
366
  __publicField(this, "_cacheOptions");
367
+ __publicField(this, "_acceptsFiles", false);
368
+ __publicField(this, "_fileOptions");
367
369
  this._actionName = _actionName;
368
370
  this._method = defaultMethod;
369
371
  }
@@ -379,11 +381,18 @@ class JayActionBuilderImpl {
379
381
  this._cacheOptions = options ?? { maxAge: 60 };
380
382
  return this;
381
383
  }
384
+ withFiles(options) {
385
+ this._acceptsFiles = true;
386
+ this._fileOptions = options;
387
+ return this;
388
+ }
382
389
  withHandler(handler) {
383
390
  const actionName = this._actionName;
384
391
  const method = this._method;
385
392
  const cacheOptions = this._cacheOptions;
386
393
  const serviceMarkers = this._services;
394
+ const acceptsFiles = this._acceptsFiles;
395
+ const fileOptions = this._fileOptions;
387
396
  const action = Object.assign(
388
397
  (input) => {
389
398
  const resolver = globalThis.__JAY_SERVICE_RESOLVER__;
@@ -396,7 +405,9 @@ class JayActionBuilderImpl {
396
405
  cacheOptions,
397
406
  services: serviceMarkers,
398
407
  handler,
399
- _brand: "JayAction"
408
+ _brand: "JayAction",
409
+ ...acceptsFiles && { acceptsFiles: true },
410
+ ...fileOptions && { fileOptions }
400
411
  }
401
412
  );
402
413
  return action;
@@ -411,6 +422,53 @@ function makeJayQuery(name) {
411
422
  function isJayAction(value) {
412
423
  return typeof value === "function" && value._brand === "JayAction" && typeof value.actionName === "string";
413
424
  }
425
+ class JayStreamBuilderImpl {
426
+ constructor(_actionName) {
427
+ __publicField(this, "_services", []);
428
+ __publicField(this, "_acceptsFiles", false);
429
+ __publicField(this, "_fileOptions");
430
+ this._actionName = _actionName;
431
+ }
432
+ withServices(...services) {
433
+ this._services = services;
434
+ return this;
435
+ }
436
+ withFiles(options) {
437
+ this._acceptsFiles = true;
438
+ this._fileOptions = options;
439
+ return this;
440
+ }
441
+ withHandler(handler) {
442
+ const actionName = this._actionName;
443
+ const serviceMarkers = this._services;
444
+ const acceptsFiles = this._acceptsFiles;
445
+ const fileOptions = this._fileOptions;
446
+ const action = Object.assign(
447
+ (input) => {
448
+ const resolver = globalThis.__JAY_SERVICE_RESOLVER__;
449
+ const resolvedServices = resolver ? resolver(serviceMarkers) : [];
450
+ return handler(input, ...resolvedServices);
451
+ },
452
+ {
453
+ actionName,
454
+ method: "POST",
455
+ isStreaming: true,
456
+ services: serviceMarkers,
457
+ handler,
458
+ _brand: "JayStreamAction",
459
+ ...acceptsFiles && { acceptsFiles: true },
460
+ ...fileOptions && { fileOptions }
461
+ }
462
+ );
463
+ return action;
464
+ }
465
+ }
466
+ function makeJayStream(name) {
467
+ return new JayStreamBuilderImpl(name);
468
+ }
469
+ function isJayStreamAction(value) {
470
+ return typeof value === "function" && value._brand === "JayStreamAction" && typeof value.actionName === "string";
471
+ }
414
472
  function makeJayInit(key) {
415
473
  const resolvedKey = key ?? "__JAY_INIT_KEY__";
416
474
  return {
@@ -455,11 +513,13 @@ export {
455
513
  forbidden,
456
514
  isJayAction,
457
515
  isJayInit,
516
+ isJayStreamAction,
458
517
  makeContractGenerator,
459
518
  makeJayAction,
460
519
  makeJayInit,
461
520
  makeJayQuery,
462
521
  makeJayStackComponent,
522
+ makeJayStream,
463
523
  notFound,
464
524
  partialRender,
465
525
  phaseOutput,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/fullstack-component",
3
- "version": "0.15.6",
3
+ "version": "0.16.1",
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.6",
30
- "@jay-framework/runtime": "^0.15.6"
29
+ "@jay-framework/component": "^0.16.1",
30
+ "@jay-framework/runtime": "^0.16.1"
31
31
  },
32
32
  "devDependencies": {
33
- "@jay-framework/dev-environment": "^0.15.6",
34
- "@jay-framework/jay-cli": "^0.15.6",
33
+ "@jay-framework/dev-environment": "^0.16.1",
34
+ "@jay-framework/jay-cli": "^0.16.1",
35
35
  "@types/express": "^5.0.2",
36
36
  "@types/node": "^22.15.21",
37
37
  "nodemon": "^3.0.3",