@uploadista/core 0.0.13 → 0.0.14

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 (97) hide show
  1. package/dist/{checksum-CtOagryS.mjs → checksum-BaO9w1gC.mjs} +2 -2
  2. package/dist/{checksum-CtOagryS.mjs.map → checksum-BaO9w1gC.mjs.map} +1 -1
  3. package/dist/{checksum-jmKtZ9W8.cjs → checksum-DXCv7Avr.cjs} +1 -1
  4. package/dist/errors/index.cjs +1 -1
  5. package/dist/errors/index.d.cts +1 -1
  6. package/dist/errors/index.d.mts +1 -1
  7. package/dist/errors/index.mjs +1 -1
  8. package/dist/flow/index.cjs +1 -1
  9. package/dist/flow/index.d.cts +5 -5
  10. package/dist/flow/index.d.mts +5 -5
  11. package/dist/flow/index.mjs +1 -1
  12. package/dist/flow-DhuIQwjv.mjs +2 -0
  13. package/dist/flow-DhuIQwjv.mjs.map +1 -0
  14. package/dist/flow-s_AlC4r5.cjs +1 -0
  15. package/dist/{index-Bi9YYid8.d.mts → index-3jSHmGwH.d.mts} +2 -2
  16. package/dist/{index-Bi9YYid8.d.mts.map → index-3jSHmGwH.d.mts.map} +1 -1
  17. package/dist/{index-4VDJDcWM.d.cts → index-5K4oXy67.d.cts} +822 -169
  18. package/dist/index-5K4oXy67.d.cts.map +1 -0
  19. package/dist/{index-RgOX4psL.d.mts → index-BB1v4Ynz.d.mts} +822 -169
  20. package/dist/index-BB1v4Ynz.d.mts.map +1 -0
  21. package/dist/{index-Cbf1OPLp.d.mts → index-Bu5i-gcV.d.mts} +2 -2
  22. package/dist/index-Bu5i-gcV.d.mts.map +1 -0
  23. package/dist/{index-De4wQJwR.d.cts → index-CHGBYDtr.d.cts} +2 -2
  24. package/dist/{index-De4wQJwR.d.cts.map → index-CHGBYDtr.d.cts.map} +1 -1
  25. package/dist/{index-qZ90PVNl.d.cts → index-T6MZvUlM.d.cts} +2 -2
  26. package/dist/{index-Cbf1OPLp.d.mts.map → index-T6MZvUlM.d.cts.map} +1 -1
  27. package/dist/index.cjs +1 -1
  28. package/dist/index.d.cts +5 -5
  29. package/dist/index.d.mts +5 -5
  30. package/dist/index.mjs +1 -1
  31. package/dist/{stream-limiter-D9rrsvAT.cjs → stream-limiter-BcTJAjs-.cjs} +1 -1
  32. package/dist/{stream-limiter-D9KSAaoY.mjs → stream-limiter-D1-sVS5i.mjs} +2 -2
  33. package/dist/{stream-limiter-D9KSAaoY.mjs.map → stream-limiter-D1-sVS5i.mjs.map} +1 -1
  34. package/dist/streams/index.cjs +1 -1
  35. package/dist/streams/index.d.cts +2 -2
  36. package/dist/streams/index.d.mts +2 -2
  37. package/dist/streams/index.mjs +1 -1
  38. package/dist/testing/index.cjs +1 -1
  39. package/dist/testing/index.d.cts +4 -4
  40. package/dist/testing/index.d.mts +4 -4
  41. package/dist/testing/index.mjs +1 -1
  42. package/dist/types/index.cjs +1 -1
  43. package/dist/types/index.d.cts +4 -4
  44. package/dist/types/index.d.mts +4 -4
  45. package/dist/types/index.mjs +1 -1
  46. package/dist/types-B-EckCWW.cjs +1 -0
  47. package/dist/types-CO-R4pFG.mjs +2 -0
  48. package/dist/types-CO-R4pFG.mjs.map +1 -0
  49. package/dist/upload/index.cjs +1 -1
  50. package/dist/upload/index.d.cts +4 -4
  51. package/dist/upload/index.d.mts +4 -4
  52. package/dist/upload/index.mjs +1 -1
  53. package/dist/{upload-D-eiOIVG.cjs → upload-BwXGQQ26.cjs} +1 -1
  54. package/dist/upload-C_Ew1NMF.mjs +2 -0
  55. package/dist/{upload-Yj5lrtZo.mjs.map → upload-C_Ew1NMF.mjs.map} +1 -1
  56. package/dist/{uploadista-error-B-n8Kfyh.cjs → uploadista-error-Blmj3lpk.cjs} +5 -1
  57. package/dist/{uploadista-error-DUWw6OqS.d.mts → uploadista-error-Cpn3uBLO.d.mts} +2 -2
  58. package/dist/uploadista-error-Cpn3uBLO.d.mts.map +1 -0
  59. package/dist/{uploadista-error-BQLhNZcY.d.cts → uploadista-error-DgdQnozn.d.cts} +2 -2
  60. package/dist/uploadista-error-DgdQnozn.d.cts.map +1 -0
  61. package/dist/{uploadista-error-Buscq-FR.mjs → uploadista-error-DhNBioWq.mjs} +5 -1
  62. package/dist/uploadista-error-DhNBioWq.mjs.map +1 -0
  63. package/dist/utils/index.cjs +1 -1
  64. package/dist/utils/index.d.cts +2 -2
  65. package/dist/utils/index.d.mts +2 -2
  66. package/dist/utils/index.mjs +1 -1
  67. package/dist/{utils-BWiu6lqv.mjs → utils-7gziergl.mjs} +2 -2
  68. package/dist/{utils-BWiu6lqv.mjs.map → utils-7gziergl.mjs.map} +1 -1
  69. package/dist/{utils-_StwBtxT.cjs → utils-C_STf6Wl.cjs} +1 -1
  70. package/package.json +3 -3
  71. package/src/errors/uploadista-error.ts +21 -1
  72. package/src/flow/event.ts +28 -4
  73. package/src/flow/flow-server.ts +43 -12
  74. package/src/flow/flow.ts +92 -13
  75. package/src/flow/index.ts +7 -0
  76. package/src/flow/node-types/index.ts +85 -0
  77. package/src/flow/node.ts +48 -6
  78. package/src/flow/nodes/input-node.ts +2 -0
  79. package/src/flow/nodes/storage-node.ts +2 -0
  80. package/src/flow/type-guards.ts +293 -0
  81. package/src/flow/type-registry.ts +345 -0
  82. package/src/flow/types/flow-job.ts +22 -6
  83. package/src/flow/types/flow-types.ts +152 -3
  84. package/tests/flow/type-system.test.ts +799 -0
  85. package/dist/flow-ChADffZ5.cjs +0 -1
  86. package/dist/flow-_J9-Dm_m.mjs +0 -2
  87. package/dist/flow-_J9-Dm_m.mjs.map +0 -1
  88. package/dist/index-4VDJDcWM.d.cts.map +0 -1
  89. package/dist/index-RgOX4psL.d.mts.map +0 -1
  90. package/dist/index-qZ90PVNl.d.cts.map +0 -1
  91. package/dist/types-BI_KmpTc.mjs +0 -2
  92. package/dist/types-BI_KmpTc.mjs.map +0 -1
  93. package/dist/types-f08UsX4E.cjs +0 -1
  94. package/dist/upload-Yj5lrtZo.mjs +0 -2
  95. package/dist/uploadista-error-BQLhNZcY.d.cts.map +0 -1
  96. package/dist/uploadista-error-Buscq-FR.mjs.map +0 -1
  97. package/dist/uploadista-error-DUWw6OqS.d.mts.map +0 -1
@@ -1,10 +1,158 @@
1
- import { n as UploadistaError } from "./uploadista-error-BQLhNZcY.cjs";
2
- import { l as GenerateId, p as GenerateIdShape } from "./index-qZ90PVNl.cjs";
1
+ import { n as UploadistaError } from "./uploadista-error-DgdQnozn.cjs";
2
+ import { l as GenerateId, p as GenerateIdShape } from "./index-T6MZvUlM.cjs";
3
3
  import { Context, Effect, Layer, Option, Stream } from "effect";
4
4
  import * as zod0 from "zod";
5
5
  import z$1, { z } from "zod";
6
6
  import * as zod_v4_core0 from "zod/v4/core";
7
7
 
8
+ //#region src/types/upload-file.d.ts
9
+
10
+ /**
11
+ * Zod schema for validating UploadFile objects.
12
+ *
13
+ * This schema defines the structure and validation rules for upload file metadata.
14
+ * Use this schema to parse and validate UploadFile data from external sources.
15
+ *
16
+ * @see {@link UploadFile} for the TypeScript type
17
+ */
18
+ declare const uploadFileSchema: z.ZodObject<{
19
+ id: z.ZodString;
20
+ size: z.ZodOptional<z.ZodNumber>;
21
+ offset: z.ZodNumber;
22
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
23
+ creationDate: z.ZodOptional<z.ZodString>;
24
+ url: z.ZodOptional<z.ZodString>;
25
+ sizeIsDeferred: z.ZodOptional<z.ZodBoolean>;
26
+ checksum: z.ZodOptional<z.ZodString>;
27
+ checksumAlgorithm: z.ZodOptional<z.ZodString>;
28
+ storage: z.ZodObject<{
29
+ id: z.ZodString;
30
+ type: z.ZodString;
31
+ path: z.ZodOptional<z.ZodString>;
32
+ uploadId: z.ZodOptional<z.ZodString>;
33
+ bucket: z.ZodOptional<z.ZodString>;
34
+ parts: z.ZodOptional<z.ZodArray<z.ZodObject<{
35
+ partNumber: z.ZodNumber;
36
+ etag: z.ZodString;
37
+ size: z.ZodNumber;
38
+ }, z.core.$strip>>>;
39
+ }, z.core.$strip>;
40
+ flow: z.ZodOptional<z.ZodObject<{
41
+ flowId: z.ZodString;
42
+ nodeId: z.ZodString;
43
+ jobId: z.ZodString;
44
+ }, z.core.$strip>>;
45
+ }, z.core.$strip>;
46
+ /**
47
+ * Represents an uploaded file with its metadata and storage information.
48
+ *
49
+ * This is the core data structure that tracks file uploads throughout their lifecycle.
50
+ * It contains all metadata needed to resume uploads, track progress, and locate files
51
+ * in storage backends.
52
+ *
53
+ * @property id - Unique identifier for this upload
54
+ * @property offset - Current byte offset (how many bytes have been uploaded)
55
+ * @property storage - Storage backend information
56
+ * @property storage.id - Storage backend identifier (e.g., "s3-production")
57
+ * @property storage.type - Storage backend type (e.g., "s3", "azure", "gcs")
58
+ * @property storage.path - Optional path prefix within the storage backend
59
+ * @property storage.uploadId - Optional backend-specific upload ID (e.g., S3 multipart upload ID)
60
+ * @property storage.bucket - Optional bucket or container name
61
+ * @property storage.parts - Optional array of uploaded parts (used by data stores that need to track parts locally, like R2)
62
+ * @property flow - Optional flow processing information (when file is part of a flow)
63
+ * @property flow.flowId - ID of the flow processing this file
64
+ * @property flow.nodeId - ID of the flow node that created this file
65
+ * @property flow.jobId - ID of the flow job execution
66
+ * @property size - Total file size in bytes (undefined if deferred)
67
+ * @property metadata - Custom key-value metadata attached to the file
68
+ * @property creationDate - ISO 8601 timestamp when upload was created
69
+ * @property url - Optional public URL to access the file
70
+ * @property sizeIsDeferred - True if file size is not known at upload start
71
+ * @property checksum - Optional file checksum/hash value
72
+ * @property checksumAlgorithm - Algorithm used for checksum (e.g., "md5", "sha256")
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * // Create an UploadFile for a new upload
77
+ * const uploadFile: UploadFile = {
78
+ * id: "upload_abc123",
79
+ * offset: 0,
80
+ * size: 1024000,
81
+ * storage: {
82
+ * id: "s3-production",
83
+ * type: "s3",
84
+ * bucket: "my-uploads",
85
+ * path: "files/"
86
+ * },
87
+ * metadata: {
88
+ * fileName: "image.jpg",
89
+ * contentType: "image/jpeg",
90
+ * userId: "user_123"
91
+ * },
92
+ * creationDate: new Date().toISOString(),
93
+ * checksum: "5d41402abc4b2a76b9719d911017c592",
94
+ * checksumAlgorithm: "md5"
95
+ * };
96
+ *
97
+ * // UploadFile with flow processing
98
+ * const flowFile: UploadFile = {
99
+ * id: "upload_xyz789",
100
+ * offset: 0,
101
+ * size: 2048000,
102
+ * storage: {
103
+ * id: "s3-temp",
104
+ * type: "s3",
105
+ * bucket: "temp-processing"
106
+ * },
107
+ * flow: {
108
+ * flowId: "flow_resize_optimize",
109
+ * nodeId: "input_1",
110
+ * jobId: "job_456"
111
+ * }
112
+ * };
113
+ *
114
+ * // Resume an interrupted upload
115
+ * const resumingFile: UploadFile = {
116
+ * id: "upload_resume",
117
+ * offset: 524288, // Already uploaded 512KB
118
+ * size: 1024000,
119
+ * storage: {
120
+ * id: "s3-production",
121
+ * type: "s3",
122
+ * uploadId: "multipart_xyz" // S3 multipart upload ID
123
+ * }
124
+ * };
125
+ * ```
126
+ */
127
+ type UploadFile = {
128
+ id: string;
129
+ offset: number;
130
+ storage: {
131
+ id: string;
132
+ type: string;
133
+ path?: string | undefined;
134
+ uploadId?: string | undefined;
135
+ bucket?: string | undefined;
136
+ parts?: Array<{
137
+ partNumber: number;
138
+ etag: string;
139
+ size: number;
140
+ }> | undefined;
141
+ };
142
+ flow?: {
143
+ flowId: string;
144
+ nodeId: string;
145
+ jobId: string;
146
+ };
147
+ size?: number | undefined;
148
+ metadata?: Record<string, string | number | boolean> | undefined;
149
+ creationDate?: string | undefined;
150
+ url?: string | undefined;
151
+ sizeIsDeferred?: boolean | undefined;
152
+ checksum?: string | undefined;
153
+ checksumAlgorithm?: string | undefined;
154
+ };
155
+ //#endregion
8
156
  //#region src/flow/node.d.ts
9
157
  /**
10
158
  * Defines the type of node in a flow, determining its role in the processing pipeline.
@@ -65,6 +213,7 @@ type ConditionValue = string | number;
65
213
  * @param config.retry.maxRetries - Maximum number of retry attempts (default: 0)
66
214
  * @param config.retry.retryDelay - Base delay in milliseconds between retries (default: 1000)
67
215
  * @param config.retry.exponentialBackoff - Whether to use exponential backoff for retries (default: true)
216
+ * @param config.nodeTypeId - Optional type ID from the registry (e.g., "storage-output-v1"). If provided, the node type must be registered and its category must match the node type (input/output).
68
217
  *
69
218
  * @returns An Effect that succeeds with the created FlowNode
70
219
  *
@@ -110,7 +259,8 @@ declare function createFlowNode<Input, Output, TType extends NodeType = NodeType
110
259
  multiInput,
111
260
  multiOutput,
112
261
  pausable,
113
- retry
262
+ retry,
263
+ nodeTypeId
114
264
  }: {
115
265
  id: string;
116
266
  name: string;
@@ -138,9 +288,10 @@ declare function createFlowNode<Input, Output, TType extends NodeType = NodeType
138
288
  retryDelay?: number;
139
289
  exponentialBackoff?: boolean;
140
290
  };
291
+ nodeTypeId?: string;
141
292
  }): Effect.Effect<FlowNode<Input, Output, UploadistaError> & {
142
293
  type: TType;
143
- }>;
294
+ }, UploadistaError>;
144
295
  /**
145
296
  * Extracts serializable node metadata from a FlowNode instance.
146
297
  *
@@ -226,12 +377,31 @@ type FlowEventFlowStart = {
226
377
  /**
227
378
  * Event emitted when a flow completes successfully.
228
379
  *
229
- * @property result - The final output from all output nodes in the flow
380
+ * @property outputs - Array of typed outputs from all output nodes in the flow
381
+ * @property result - Legacy field for backward compatibility (deprecated, use outputs instead)
382
+ *
383
+ * @remarks
384
+ * The `outputs` field contains an array of TypedOutput objects, each with:
385
+ * - nodeId: The specific node that produced the output
386
+ * - nodeType: The registered type ID (e.g., "storage-output-v1")
387
+ * - data: The actual output data
388
+ * - timestamp: When the output was produced
389
+ *
390
+ * @example
391
+ * ```typescript
392
+ * // Handle flow completion with typed outputs
393
+ * if (event.eventType === EventType.FlowEnd && event.outputs) {
394
+ * for (const output of event.outputs) {
395
+ * console.log(`${output.nodeId} (${output.nodeType}):`, output.data);
396
+ * }
397
+ * }
398
+ * ```
230
399
  */
231
400
  type FlowEventFlowEnd = {
232
401
  jobId: string;
233
402
  flowId: string;
234
403
  eventType: EventType.FlowEnd;
404
+ outputs?: TypedOutput[];
235
405
  result?: unknown;
236
406
  };
237
407
  /**
@@ -299,7 +469,11 @@ type FlowEventNodeError = {
299
469
  /**
300
470
  * Event emitted when a node completes successfully.
301
471
  *
302
- * @property result - The output data produced by the node
472
+ * @property result - The typed output data produced by the node
473
+ *
474
+ * @remarks
475
+ * For output nodes, the result will be a TypedOutput containing type information.
476
+ * For other nodes, it may be untyped (nodeType will be undefined).
303
477
  */
304
478
  type FlowEventNodeEnd = {
305
479
  jobId: string;
@@ -307,7 +481,7 @@ type FlowEventNodeEnd = {
307
481
  nodeId: string;
308
482
  eventType: EventType.NodeEnd;
309
483
  nodeName: string;
310
- result?: unknown;
484
+ result?: TypedOutput | unknown;
311
485
  };
312
486
  /**
313
487
  * Event emitted when a node pauses execution, waiting for additional data.
@@ -402,6 +576,134 @@ type FlowNodeData = {
402
576
  description: string;
403
577
  type: NodeType;
404
578
  };
579
+ /**
580
+ * Built-in typed outputs with automatic TypeScript narrowing.
581
+ *
582
+ * These outputs use discriminated unions to enable automatic type narrowing
583
+ * in switch statements without requiring type guards.
584
+ *
585
+ * @remarks
586
+ * Built-in types automatically narrow when using switch statements:
587
+ * ```typescript
588
+ * switch (output.nodeType) {
589
+ * case 'storage-output-v1':
590
+ * output.data.url // ✅ TypeScript knows data is UploadFile
591
+ * break;
592
+ * }
593
+ * ```
594
+ */
595
+ type BuiltInTypedOutput = {
596
+ nodeType: "storage-output-v1";
597
+ data: UploadFile;
598
+ nodeId: string;
599
+ timestamp: string;
600
+ } | {
601
+ nodeType: "streaming-input-v1";
602
+ data: UploadFile;
603
+ nodeId: string;
604
+ timestamp: string;
605
+ };
606
+ /**
607
+ * Custom typed output for user-defined node types.
608
+ *
609
+ * Custom outputs require type guards for type narrowing:
610
+ * ```typescript
611
+ * if (isThumbnailOutput(output)) {
612
+ * output.data.width // ✅ Type guard narrows data to ThumbnailOutput
613
+ * }
614
+ * ```
615
+ *
616
+ * @template T - The TypeScript type of the output data
617
+ */
618
+ type CustomTypedOutput<T = unknown> = {
619
+ nodeType?: string;
620
+ data: T;
621
+ nodeId: string;
622
+ timestamp: string;
623
+ };
624
+ /**
625
+ * Typed output structure from a flow node.
626
+ *
627
+ * This is a discriminated union that provides automatic type narrowing for
628
+ * built-in types while maintaining extensibility for custom types.
629
+ *
630
+ * @template T - The TypeScript type of the output data (for custom outputs)
631
+ *
632
+ * @property nodeId - Node instance ID that produced this output
633
+ * @property nodeType - Type ID from the registry (e.g., "storage-output-v1")
634
+ * @property data - The actual output data from the node
635
+ * @property timestamp - ISO 8601 timestamp when the result was produced
636
+ *
637
+ * @remarks
638
+ * **Built-in types (automatic narrowing):**
639
+ * - `storage-output-v1` - Storage node output (UploadFile)
640
+ * - `streaming-input-v1` - Streaming input node (UploadFile)
641
+ *
642
+ * Use switch statements for automatic narrowing:
643
+ * ```typescript
644
+ * for (const output of state.flowOutputs) {
645
+ * switch (output.nodeType) {
646
+ * case 'storage-output-v1':
647
+ * // ✅ output.data is automatically UploadFile
648
+ * console.log(output.data.url);
649
+ * break;
650
+ * case 'streaming-input-v1':
651
+ * // ✅ output.data is automatically UploadFile
652
+ * console.log(output.data.size);
653
+ * break;
654
+ * }
655
+ * }
656
+ * ```
657
+ *
658
+ * **Custom types (require type guards):**
659
+ * ```typescript
660
+ * import { isThumbnailOutput } from './type-guards';
661
+ *
662
+ * if (isThumbnailOutput(output)) {
663
+ * // ✅ Type guard narrows output.data to ThumbnailOutput
664
+ * console.log(output.data.width);
665
+ * }
666
+ * ```
667
+ *
668
+ * **Untyped nodes (backward compatible):**
669
+ * ```typescript
670
+ * const untypedOutput: TypedOutput = {
671
+ * nodeId: "custom-node-1",
672
+ * data: { custom: "data" },
673
+ * timestamp: "2024-01-15T10:30:00Z"
674
+ * };
675
+ * ```
676
+ *
677
+ * @example
678
+ * ```typescript
679
+ * // Storage output result (built-in, automatic narrowing)
680
+ * const output: TypedOutput = {
681
+ * nodeId: "storage-1",
682
+ * nodeType: "storage-output-v1",
683
+ * data: {
684
+ * id: "file-123",
685
+ * url: "https://cdn.example.com/file.jpg",
686
+ * size: 1024000,
687
+ * // ... rest of UploadFile
688
+ * },
689
+ * timestamp: "2024-01-15T10:30:00Z"
690
+ * };
691
+ *
692
+ * // Custom output (requires type guard)
693
+ * const thumbnailOutput: TypedOutput<ThumbnailOutput> = {
694
+ * nodeId: "thumbnail-1",
695
+ * nodeType: "thumbnail-output-v1",
696
+ * data: {
697
+ * url: "https://cdn.example.com/thumb.jpg",
698
+ * width: 200,
699
+ * height: 200,
700
+ * format: "webp",
701
+ * },
702
+ * timestamp: "2024-01-15T10:30:00Z"
703
+ * };
704
+ * ```
705
+ */
706
+ type TypedOutput<T = unknown> = BuiltInTypedOutput | CustomTypedOutput<T>;
405
707
  /**
406
708
  * Result of a node execution - either complete or waiting for more data.
407
709
  *
@@ -412,10 +714,15 @@ type FlowNodeData = {
412
714
  * data (e.g., chunked uploads, external service responses). The flow can be
413
715
  * resumed later with the missing data.
414
716
  *
717
+ * Results now include optional type information (`nodeType` and `nodeId`) to
718
+ * enable type-safe result consumption. These fields are automatically added
719
+ * by the node execution wrapper when a node is created with a `nodeTypeId`.
720
+ *
415
721
  * @example
416
722
  * ```typescript
417
- * // Node completes immediately
723
+ * // Node completes immediately with type information
418
724
  * return completeNodeExecution({ processedData });
725
+ * // Result will be wrapped with: { type: "complete", data, nodeType, nodeId }
419
726
  *
420
727
  * // Node waits for more chunks
421
728
  * if (needsMoreData) {
@@ -426,9 +733,13 @@ type FlowNodeData = {
426
733
  type NodeExecutionResult<TOutput> = {
427
734
  type: "complete";
428
735
  data: TOutput;
736
+ nodeType?: string;
737
+ nodeId?: string;
429
738
  } | {
430
739
  type: "waiting";
431
740
  partialData?: unknown;
741
+ nodeType?: string;
742
+ nodeId?: string;
432
743
  };
433
744
  /**
434
745
  * Helper function to create a complete node execution result.
@@ -790,6 +1101,7 @@ declare const getFlowData: <TRequirements>(flow: Flow<any, any, TRequirements>)
790
1101
  type FlowExecutionResult<TOutput> = {
791
1102
  type: "completed";
792
1103
  result: TOutput;
1104
+ outputs?: TypedOutput[];
793
1105
  } | {
794
1106
  type: "paused";
795
1107
  nodeId: string;
@@ -950,159 +1262,301 @@ type Flow<TFlowInputSchema extends z.ZodSchema<any>, TFlowOutputSchema extends z
950
1262
  * return { eventId: event.jobId };
951
1263
  * })
952
1264
  * });
953
- * ```
1265
+ * ```
1266
+ *
1267
+ * @see {@link Flow} for the returned flow type
1268
+ * @see {@link FlowConfig} for configuration options
1269
+ */
1270
+ declare function createFlowWithSchema<TFlowInputSchema extends z.ZodSchema<any>, TFlowOutputSchema extends z.ZodSchema<any>, TRequirements = never, TNodeError = never, TNodeRequirements = never>(config: FlowConfig<TFlowInputSchema, TFlowOutputSchema, TNodeError, TNodeRequirements>): Effect.Effect<Flow<TFlowInputSchema, TFlowOutputSchema, TRequirements>, TNodeError, TNodeRequirements>;
1271
+ //#endregion
1272
+ //#region src/flow/type-registry.d.ts
1273
+ /**
1274
+ * Node type category - determines where the node appears in the flow.
1275
+ *
1276
+ * - `input`: Nodes that receive data from external sources (e.g., file uploads)
1277
+ * - `output`: Nodes that produce final results (e.g., storage, webhooks, descriptions)
1278
+ */
1279
+ type NodeTypeCategory = "input" | "output";
1280
+ /**
1281
+ * Defines a registered node type with its schema and metadata.
1282
+ *
1283
+ * Node type definitions are registered globally and used to validate and type-narrow
1284
+ * flow results at runtime. Each definition includes:
1285
+ * - A unique identifier with versioning
1286
+ * - A category (input or output)
1287
+ * - A Zod schema for runtime validation
1288
+ * - A semantic version for evolution
1289
+ * - A human-readable description
1290
+ *
1291
+ * @template TSchema - The Zod schema type for this node's data
1292
+ *
1293
+ * @property id - Unique identifier (e.g., "storage-output-v1", "webhook-output-v1")
1294
+ * @property category - Whether this is an input or output node type
1295
+ * @property schema - Zod schema for validating data produced by this node type
1296
+ * @property version - Semantic version (e.g., "1.0.0") for tracking type evolution
1297
+ * @property description - Human-readable explanation of what this node type does
1298
+ *
1299
+ * @example
1300
+ * ```typescript
1301
+ * const storageOutputDef: NodeTypeDefinition<z.infer<typeof uploadFileSchema>> = {
1302
+ * id: "storage-output-v1",
1303
+ * category: "output",
1304
+ * schema: uploadFileSchema,
1305
+ * version: "1.0.0",
1306
+ * description: "Storage output node that saves files to configured storage backend",
1307
+ * };
1308
+ * ```
1309
+ */
1310
+ interface NodeTypeDefinition<TSchema = unknown> {
1311
+ id: string;
1312
+ category: NodeTypeCategory;
1313
+ schema: z.ZodSchema<TSchema>;
1314
+ version: string;
1315
+ description: string;
1316
+ }
1317
+ /**
1318
+ * Result type for validation operations.
1319
+ *
1320
+ * @template T - The expected type on successful validation
1321
+ */
1322
+ type ValidationResult<T> = {
1323
+ success: true;
1324
+ data: T;
1325
+ } | {
1326
+ success: false;
1327
+ error: UploadistaError;
1328
+ };
1329
+ /**
1330
+ * Central registry for node type definitions.
1331
+ *
1332
+ * The FlowTypeRegistry maintains a global registry of node types with their schemas
1333
+ * and metadata. It provides methods for:
1334
+ * - Registering new node types
1335
+ * - Retrieving type definitions
1336
+ * - Listing types by category
1337
+ * - Validating data against registered schemas
1338
+ *
1339
+ * The registry is immutable after registration - types cannot be modified or removed
1340
+ * once registered to prevent runtime errors.
1341
+ *
1342
+ * @remarks
1343
+ * - This is a singleton - use the exported `flowTypeRegistry` instance
1344
+ * - Types cannot be unregistered or modified after registration
1345
+ * - Duplicate type IDs are rejected
1346
+ * - Version strings should follow semantic versioning
1347
+ *
1348
+ * @example
1349
+ * ```typescript
1350
+ * // Register a new type
1351
+ * flowTypeRegistry.register({
1352
+ * id: "webhook-output-v1",
1353
+ * category: "output",
1354
+ * schema: webhookResponseSchema,
1355
+ * version: "1.0.0",
1356
+ * description: "HTTP webhook notification output",
1357
+ * });
1358
+ *
1359
+ * // Retrieve a type definition
1360
+ * const def = flowTypeRegistry.get("webhook-output-v1");
1361
+ * if (def) {
1362
+ * console.log(def.description);
1363
+ * }
1364
+ *
1365
+ * // List all output types
1366
+ * const outputTypes = flowTypeRegistry.listByCategory("output");
1367
+ * console.log(outputTypes.map(t => t.id));
1368
+ *
1369
+ * // Validate data
1370
+ * const result = flowTypeRegistry.validate("webhook-output-v1", data);
1371
+ * if (result.success) {
1372
+ * // data is now typed according to the schema
1373
+ * processWebhookResponse(result.data);
1374
+ * }
1375
+ * ```
1376
+ */
1377
+ declare class FlowTypeRegistry {
1378
+ private readonly types;
1379
+ constructor();
1380
+ /**
1381
+ * Register a new node type in the registry.
1382
+ *
1383
+ * Once registered, a type cannot be modified or removed. Attempting to register
1384
+ * a type with a duplicate ID will throw an error.
1385
+ *
1386
+ * @template T - The TypeScript type inferred from the Zod schema
1387
+ * @param definition - The complete type definition including schema and metadata
1388
+ * @throws {UploadistaError} If a type with the same ID is already registered
1389
+ *
1390
+ * @example
1391
+ * ```typescript
1392
+ * import { z } from "zod";
1393
+ *
1394
+ * flowTypeRegistry.register({
1395
+ * id: "description-output-v1",
1396
+ * category: "output",
1397
+ * schema: z.object({
1398
+ * description: z.string(),
1399
+ * confidence: z.number().min(0).max(1),
1400
+ * tags: z.array(z.string()).optional(),
1401
+ * }),
1402
+ * version: "1.0.0",
1403
+ * description: "AI-generated image description with confidence score",
1404
+ * });
1405
+ * ```
1406
+ */
1407
+ register<T>(definition: NodeTypeDefinition<T>): void;
1408
+ /**
1409
+ * Retrieve a registered type definition by its ID.
1410
+ *
1411
+ * @param id - The unique type identifier (e.g., "storage-output-v1")
1412
+ * @returns The type definition if found, undefined otherwise
1413
+ *
1414
+ * @example
1415
+ * ```typescript
1416
+ * const def = flowTypeRegistry.get("storage-output-v1");
1417
+ * if (def) {
1418
+ * console.log(`Found ${def.description} (v${def.version})`);
1419
+ * } else {
1420
+ * console.warn("Type not registered");
1421
+ * }
1422
+ * ```
1423
+ */
1424
+ get(id: string): NodeTypeDefinition<unknown> | undefined;
1425
+ /**
1426
+ * List all registered types in a specific category.
1427
+ *
1428
+ * @param category - The node category to filter by ("input" or "output")
1429
+ * @returns Array of type definitions in the specified category
1430
+ *
1431
+ * @example
1432
+ * ```typescript
1433
+ * // List all registered output types
1434
+ * const outputTypes = flowTypeRegistry.listByCategory("output");
1435
+ * console.log("Available output types:");
1436
+ * for (const type of outputTypes) {
1437
+ * console.log(`- ${type.id}: ${type.description}`);
1438
+ * }
1439
+ * ```
1440
+ */
1441
+ listByCategory(category: NodeTypeCategory): NodeTypeDefinition<unknown>[];
1442
+ /**
1443
+ * Validate data against a registered type's schema.
1444
+ *
1445
+ * This method performs runtime validation using the Zod schema associated with
1446
+ * the type. If validation succeeds, the data is returned with proper typing.
1447
+ * If validation fails, an UploadistaError is returned with details.
1448
+ *
1449
+ * @template T - The expected TypeScript type after validation
1450
+ * @param typeId - The ID of the registered type to validate against
1451
+ * @param data - The data to validate
1452
+ * @returns A result object with either the validated data or an error
1453
+ *
1454
+ * @example
1455
+ * ```typescript
1456
+ * const result = flowTypeRegistry.validate("storage-output-v1", unknownData);
1457
+ *
1458
+ * if (result.success) {
1459
+ * // TypeScript knows result.data is an UploadFile
1460
+ * console.log(`File stored at: ${result.data.url}`);
1461
+ * } else {
1462
+ * console.error(`Validation failed: ${result.error.body}`);
1463
+ * }
1464
+ * ```
1465
+ */
1466
+ validate<T>(typeId: string, data: unknown): ValidationResult<T>;
1467
+ /**
1468
+ * Check if a type is registered.
1469
+ *
1470
+ * @param id - The unique type identifier to check
1471
+ * @returns True if the type is registered, false otherwise
1472
+ *
1473
+ * @example
1474
+ * ```typescript
1475
+ * if (flowTypeRegistry.has("custom-output-v1")) {
1476
+ * console.log("Custom output type is available");
1477
+ * }
1478
+ * ```
1479
+ */
1480
+ has(id: string): boolean;
1481
+ /**
1482
+ * Get the total number of registered types.
1483
+ *
1484
+ * @returns The count of registered types
1485
+ *
1486
+ * @example
1487
+ * ```typescript
1488
+ * console.log(`Registry contains ${flowTypeRegistry.size()} types`);
1489
+ * ```
1490
+ */
1491
+ size(): number;
1492
+ }
1493
+ /**
1494
+ * Global singleton instance of the flow type registry.
1495
+ *
1496
+ * Use this instance to register and access node type definitions throughout
1497
+ * your application. The registry is initialized once and shared globally.
1498
+ *
1499
+ * @example
1500
+ * ```typescript
1501
+ * import { flowTypeRegistry } from "@uploadista/core/flow";
1502
+ *
1503
+ * // Register a type
1504
+ * flowTypeRegistry.register({
1505
+ * id: "my-output-v1",
1506
+ * category: "output",
1507
+ * schema: mySchema,
1508
+ * version: "1.0.0",
1509
+ * description: "My custom output type",
1510
+ * });
954
1511
  *
955
- * @see {@link Flow} for the returned flow type
956
- * @see {@link FlowConfig} for configuration options
1512
+ * // Validate data
1513
+ * const result = flowTypeRegistry.validate("my-output-v1", data);
1514
+ * ```
957
1515
  */
958
- declare function createFlowWithSchema<TFlowInputSchema extends z.ZodSchema<any>, TFlowOutputSchema extends z.ZodSchema<any>, TRequirements = never, TNodeError = never, TNodeRequirements = never>(config: FlowConfig<TFlowInputSchema, TFlowOutputSchema, TNodeError, TNodeRequirements>): Effect.Effect<Flow<TFlowInputSchema, TFlowOutputSchema, TRequirements>, TNodeError, TNodeRequirements>;
1516
+ declare const flowTypeRegistry: FlowTypeRegistry;
959
1517
  //#endregion
960
- //#region src/types/upload-file.d.ts
1518
+ //#region src/flow/node-types/index.d.ts
961
1519
  /**
962
- * Zod schema for validating UploadFile objects.
1520
+ * Built-in node type registrations for the flow engine.
963
1521
  *
964
- * This schema defines the structure and validation rules for upload file metadata.
965
- * Use this schema to parse and validate UploadFile data from external sources.
1522
+ * This module automatically registers the standard input and output node types
1523
+ * when imported. These types enable type-safe result consumption in clients.
966
1524
  *
967
- * @see {@link UploadFile} for the TypeScript type
1525
+ * @module flow/node-types
1526
+ *
1527
+ * @remarks
1528
+ * This module should be imported by the flow engine initialization to ensure
1529
+ * built-in types are registered before any flows are created.
1530
+ *
1531
+ * @example
1532
+ * ```typescript
1533
+ * // Types are automatically registered on import
1534
+ * import "@uploadista/core/flow/node-types";
1535
+ * import { flowTypeRegistry } from "@uploadista/core/flow";
1536
+ *
1537
+ * // Check registered types
1538
+ * const inputTypes = flowTypeRegistry.listByCategory("input");
1539
+ * console.log(inputTypes.map(t => t.id)); // ["streaming-input-v1"]
1540
+ * ```
968
1541
  */
969
- declare const uploadFileSchema: z.ZodObject<{
970
- id: z.ZodString;
971
- size: z.ZodOptional<z.ZodNumber>;
972
- offset: z.ZodNumber;
973
- metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
974
- creationDate: z.ZodOptional<z.ZodString>;
975
- url: z.ZodOptional<z.ZodString>;
976
- sizeIsDeferred: z.ZodOptional<z.ZodBoolean>;
977
- checksum: z.ZodOptional<z.ZodString>;
978
- checksumAlgorithm: z.ZodOptional<z.ZodString>;
979
- storage: z.ZodObject<{
980
- id: z.ZodString;
981
- type: z.ZodString;
982
- path: z.ZodOptional<z.ZodString>;
983
- uploadId: z.ZodOptional<z.ZodString>;
984
- bucket: z.ZodOptional<z.ZodString>;
985
- parts: z.ZodOptional<z.ZodArray<z.ZodObject<{
986
- partNumber: z.ZodNumber;
987
- etag: z.ZodString;
988
- size: z.ZodNumber;
989
- }, z.core.$strip>>>;
990
- }, z.core.$strip>;
991
- flow: z.ZodOptional<z.ZodObject<{
992
- flowId: z.ZodString;
993
- nodeId: z.ZodString;
994
- jobId: z.ZodString;
995
- }, z.core.$strip>>;
996
- }, z.core.$strip>;
997
1542
  /**
998
- * Represents an uploaded file with its metadata and storage information.
999
- *
1000
- * This is the core data structure that tracks file uploads throughout their lifecycle.
1001
- * It contains all metadata needed to resume uploads, track progress, and locate files
1002
- * in storage backends.
1543
+ * Type ID constants for built-in node types.
1003
1544
  *
1004
- * @property id - Unique identifier for this upload
1005
- * @property offset - Current byte offset (how many bytes have been uploaded)
1006
- * @property storage - Storage backend information
1007
- * @property storage.id - Storage backend identifier (e.g., "s3-production")
1008
- * @property storage.type - Storage backend type (e.g., "s3", "azure", "gcs")
1009
- * @property storage.path - Optional path prefix within the storage backend
1010
- * @property storage.uploadId - Optional backend-specific upload ID (e.g., S3 multipart upload ID)
1011
- * @property storage.bucket - Optional bucket or container name
1012
- * @property storage.parts - Optional array of uploaded parts (used by data stores that need to track parts locally, like R2)
1013
- * @property flow - Optional flow processing information (when file is part of a flow)
1014
- * @property flow.flowId - ID of the flow processing this file
1015
- * @property flow.nodeId - ID of the flow node that created this file
1016
- * @property flow.jobId - ID of the flow job execution
1017
- * @property size - Total file size in bytes (undefined if deferred)
1018
- * @property metadata - Custom key-value metadata attached to the file
1019
- * @property creationDate - ISO 8601 timestamp when upload was created
1020
- * @property url - Optional public URL to access the file
1021
- * @property sizeIsDeferred - True if file size is not known at upload start
1022
- * @property checksum - Optional file checksum/hash value
1023
- * @property checksumAlgorithm - Algorithm used for checksum (e.g., "md5", "sha256")
1545
+ * Use these constants when creating nodes with type information to ensure
1546
+ * consistency and avoid typos.
1024
1547
  *
1025
1548
  * @example
1026
1549
  * ```typescript
1027
- * // Create an UploadFile for a new upload
1028
- * const uploadFile: UploadFile = {
1029
- * id: "upload_abc123",
1030
- * offset: 0,
1031
- * size: 1024000,
1032
- * storage: {
1033
- * id: "s3-production",
1034
- * type: "s3",
1035
- * bucket: "my-uploads",
1036
- * path: "files/"
1037
- * },
1038
- * metadata: {
1039
- * fileName: "image.jpg",
1040
- * contentType: "image/jpeg",
1041
- * userId: "user_123"
1042
- * },
1043
- * creationDate: new Date().toISOString(),
1044
- * checksum: "5d41402abc4b2a76b9719d911017c592",
1045
- * checksumAlgorithm: "md5"
1046
- * };
1047
- *
1048
- * // UploadFile with flow processing
1049
- * const flowFile: UploadFile = {
1050
- * id: "upload_xyz789",
1051
- * offset: 0,
1052
- * size: 2048000,
1053
- * storage: {
1054
- * id: "s3-temp",
1055
- * type: "s3",
1056
- * bucket: "temp-processing"
1057
- * },
1058
- * flow: {
1059
- * flowId: "flow_resize_optimize",
1060
- * nodeId: "input_1",
1061
- * jobId: "job_456"
1062
- * }
1063
- * };
1550
+ * import { STREAMING_INPUT_TYPE_ID } from "@uploadista/core/flow/node-types";
1064
1551
  *
1065
- * // Resume an interrupted upload
1066
- * const resumingFile: UploadFile = {
1067
- * id: "upload_resume",
1068
- * offset: 524288, // Already uploaded 512KB
1069
- * size: 1024000,
1070
- * storage: {
1071
- * id: "s3-production",
1072
- * type: "s3",
1073
- * uploadId: "multipart_xyz" // S3 multipart upload ID
1074
- * }
1075
- * };
1552
+ * const inputNode = createFlowNode({
1553
+ * // ... other config
1554
+ * nodeTypeId: STREAMING_INPUT_TYPE_ID
1555
+ * });
1076
1556
  * ```
1077
1557
  */
1078
- type UploadFile = {
1079
- id: string;
1080
- offset: number;
1081
- storage: {
1082
- id: string;
1083
- type: string;
1084
- path?: string | undefined;
1085
- uploadId?: string | undefined;
1086
- bucket?: string | undefined;
1087
- parts?: Array<{
1088
- partNumber: number;
1089
- etag: string;
1090
- size: number;
1091
- }> | undefined;
1092
- };
1093
- flow?: {
1094
- flowId: string;
1095
- nodeId: string;
1096
- jobId: string;
1097
- };
1098
- size?: number | undefined;
1099
- metadata?: Record<string, string | number | boolean> | undefined;
1100
- creationDate?: string | undefined;
1101
- url?: string | undefined;
1102
- sizeIsDeferred?: boolean | undefined;
1103
- checksum?: string | undefined;
1104
- checksumAlgorithm?: string | undefined;
1105
- };
1558
+ declare const STREAMING_INPUT_TYPE_ID = "streaming-input-v1";
1559
+ declare const STORAGE_OUTPUT_TYPE_ID = "storage-output-v1";
1106
1560
  //#endregion
1107
1561
  //#region src/types/kv-store.d.ts
1108
1562
  /**
@@ -2465,6 +2919,201 @@ declare const MiddlewareService_base: Context.TagClass<MiddlewareService, "Middl
2465
2919
  declare class MiddlewareService extends MiddlewareService_base {}
2466
2920
  declare const MiddlewareServiceLive: Layer.Layer<MiddlewareService, never, never>;
2467
2921
  //#endregion
2922
+ //#region src/flow/type-guards.d.ts
2923
+ /**
2924
+ * Factory function to create type guards for specific node types.
2925
+ *
2926
+ * Creates a TypeScript type guard that validates both the type tag and
2927
+ * the data structure against the registered schema. This enables type-safe
2928
+ * narrowing of TypedOutput objects in TypeScript.
2929
+ *
2930
+ * @template T - The expected TypeScript type after narrowing
2931
+ * @param typeId - The registered type ID to check against (e.g., "storage-output-v1")
2932
+ * @returns A type guard function that narrows TypedOutput to TypedOutput<T>
2933
+ *
2934
+ * @example
2935
+ * ```typescript
2936
+ * import { createTypeGuard } from "@uploadista/core/flow";
2937
+ * import { z } from "zod";
2938
+ *
2939
+ * const descriptionSchema = z.object({
2940
+ * description: z.string(),
2941
+ * confidence: z.number(),
2942
+ * });
2943
+ *
2944
+ * type DescriptionOutput = z.infer<typeof descriptionSchema>;
2945
+ *
2946
+ * const isDescriptionOutput = createTypeGuard<DescriptionOutput>(
2947
+ * "description-output-v1"
2948
+ * );
2949
+ *
2950
+ * // Use in code
2951
+ * if (isDescriptionOutput(output)) {
2952
+ * // output.data is typed as DescriptionOutput
2953
+ * console.log(output.data.description);
2954
+ * }
2955
+ * ```
2956
+ */
2957
+ declare function createTypeGuard<T>(typeId: string): (output: TypedOutput) => output is TypedOutput<T>;
2958
+ /**
2959
+ * Type guard for storage output nodes.
2960
+ *
2961
+ * Validates that an output is from a storage node and contains valid UploadFile data.
2962
+ *
2963
+ * @param output - The output to check
2964
+ * @returns True if the output is a storage output with valid UploadFile data
2965
+ *
2966
+ * @example
2967
+ * ```typescript
2968
+ * import { isStorageOutput } from "@uploadista/core/flow";
2969
+ *
2970
+ * if (isStorageOutput(output)) {
2971
+ * // output.data is typed as UploadFile
2972
+ * console.log("File URL:", output.data.url);
2973
+ * console.log("File size:", output.data.size);
2974
+ * }
2975
+ * ```
2976
+ */
2977
+ declare const isStorageOutput: (output: TypedOutput) => output is TypedOutput<UploadFile>;
2978
+ /**
2979
+ * Filter an array of outputs to only those matching a specific type.
2980
+ *
2981
+ * This helper function filters outputs using a type guard and returns a
2982
+ * properly typed array of results. It's useful for extracting specific
2983
+ * output types from multi-output flows.
2984
+ *
2985
+ * @template T - The expected output data type
2986
+ * @param outputs - Array of typed outputs to filter
2987
+ * @param typeGuard - Type guard function to use for filtering
2988
+ * @returns Array of outputs that match the type guard, properly typed
2989
+ *
2990
+ * @example
2991
+ * ```typescript
2992
+ * import { filterOutputsByType, isStorageOutput } from "@uploadista/core/flow";
2993
+ *
2994
+ * // Get all storage outputs from a multi-output flow
2995
+ * const storageOutputs = filterOutputsByType(
2996
+ * flowResult.outputs,
2997
+ * isStorageOutput
2998
+ * );
2999
+ *
3000
+ * for (const output of storageOutputs) {
3001
+ * // Each output.data is typed as UploadFile
3002
+ * console.log("Saved file:", output.data.url);
3003
+ * }
3004
+ * ```
3005
+ */
3006
+ declare function filterOutputsByType<T>(outputs: TypedOutput[], typeGuard: (output: TypedOutput) => output is TypedOutput<T>): TypedOutput<T>[];
3007
+ /**
3008
+ * Get a single output of a specific type from an array of outputs.
3009
+ *
3010
+ * This helper function finds exactly one output matching the type guard.
3011
+ * It throws an error if no outputs match or if multiple outputs match,
3012
+ * ensuring the caller receives exactly the expected result.
3013
+ *
3014
+ * @template T - The expected output data type
3015
+ * @param outputs - Array of typed outputs to search
3016
+ * @param typeGuard - Type guard function to use for matching
3017
+ * @returns The single matching output, properly typed
3018
+ * @throws {UploadistaError} If no outputs match (OUTPUT_NOT_FOUND)
3019
+ * @throws {UploadistaError} If multiple outputs match (MULTIPLE_OUTPUTS_FOUND)
3020
+ *
3021
+ * @example
3022
+ * ```typescript
3023
+ * import { getSingleOutputByType, isStorageOutput } from "@uploadista/core/flow";
3024
+ *
3025
+ * try {
3026
+ * const storageOutput = getSingleOutputByType(
3027
+ * flowResult.outputs,
3028
+ * isStorageOutput
3029
+ * );
3030
+ * // storageOutput.data is typed as UploadFile
3031
+ * console.log("File saved at:", storageOutput.data.url);
3032
+ * } catch (error) {
3033
+ * if (error.code === "OUTPUT_NOT_FOUND") {
3034
+ * console.error("No storage output found");
3035
+ * } else if (error.code === "MULTIPLE_OUTPUTS_FOUND") {
3036
+ * console.error("Multiple storage outputs found, expected one");
3037
+ * }
3038
+ * }
3039
+ * ```
3040
+ */
3041
+ declare function getSingleOutputByType<T>(outputs: TypedOutput[], typeGuard: (output: TypedOutput) => output is TypedOutput<T>): Effect.Effect<TypedOutput<T>, UploadistaError>;
3042
+ /**
3043
+ * Get the first output of a specific type, if any exists.
3044
+ *
3045
+ * Unlike getSingleOutputByType, this function returns undefined if no outputs
3046
+ * match, and returns the first match if multiple outputs exist. This is useful
3047
+ * when you want a more lenient matching strategy.
3048
+ *
3049
+ * @template T - The expected output data type
3050
+ * @param outputs - Array of typed outputs to search
3051
+ * @param typeGuard - Type guard function to use for matching
3052
+ * @returns The first matching output, or undefined if none match
3053
+ *
3054
+ * @example
3055
+ * ```typescript
3056
+ * import { getFirstOutputByType, isStorageOutput } from "@uploadista/core/flow";
3057
+ *
3058
+ * const storageOutput = getFirstOutputByType(
3059
+ * flowResult.outputs,
3060
+ * isStorageOutput
3061
+ * );
3062
+ *
3063
+ * if (storageOutput) {
3064
+ * console.log("First storage output:", storageOutput.data.url);
3065
+ * } else {
3066
+ * console.log("No storage outputs found");
3067
+ * }
3068
+ * ```
3069
+ */
3070
+ declare function getFirstOutputByType<T>(outputs: TypedOutput[], typeGuard: (output: TypedOutput) => output is TypedOutput<T>): TypedOutput<T> | undefined;
3071
+ /**
3072
+ * Get an output by its node ID.
3073
+ *
3074
+ * This helper finds an output produced by a specific node instance,
3075
+ * regardless of its type. Useful when you know the specific node ID
3076
+ * you're looking for.
3077
+ *
3078
+ * @param outputs - Array of typed outputs to search
3079
+ * @param nodeId - The node ID to match
3080
+ * @returns The output from the specified node, or undefined if not found
3081
+ *
3082
+ * @example
3083
+ * ```typescript
3084
+ * import { getOutputByNodeId } from "@uploadista/core/flow";
3085
+ *
3086
+ * const cdnOutput = getOutputByNodeId(flowResult.outputs, "cdn-storage");
3087
+ * if (cdnOutput) {
3088
+ * console.log("CDN output:", cdnOutput.data);
3089
+ * }
3090
+ * ```
3091
+ */
3092
+ declare function getOutputByNodeId(outputs: TypedOutput[], nodeId: string): TypedOutput | undefined;
3093
+ /**
3094
+ * Check if any outputs match a specific type.
3095
+ *
3096
+ * Simple predicate function to check if at least one output of a given
3097
+ * type exists in the results.
3098
+ *
3099
+ * @template T - The expected output data type
3100
+ * @param outputs - Array of typed outputs to check
3101
+ * @param typeGuard - Type guard function to use for checking
3102
+ * @returns True if at least one output matches the type guard
3103
+ *
3104
+ * @example
3105
+ * ```typescript
3106
+ * import { hasOutputOfType, isStorageOutput } from "@uploadista/core/flow";
3107
+ *
3108
+ * if (hasOutputOfType(flowResult.outputs, isStorageOutput)) {
3109
+ * console.log("Flow produced at least one storage output");
3110
+ * } else {
3111
+ * console.log("No storage outputs in this flow");
3112
+ * }
3113
+ * ```
3114
+ */
3115
+ declare function hasOutputOfType<T>(outputs: TypedOutput[], typeGuard: (output: TypedOutput) => output is TypedOutput<T>): boolean;
3116
+ //#endregion
2468
3117
  //#region src/upload/mime.d.ts
2469
3118
  /**
2470
3119
  * Detect MIME type from buffer using magic bytes (file signatures).
@@ -2672,7 +3321,7 @@ declare function createUploadServer(): Effect.Effect<{
2672
3321
  getCapabilities: (storageId: string, clientId: string | null) => Effect.Effect<DataStoreCapabilities, UploadistaError, never>;
2673
3322
  subscribeToUploadEvents: (uploadId: string, connection: WebSocketConnection) => Effect.Effect<void, UploadistaError, never>;
2674
3323
  unsubscribeFromUploadEvents: (uploadId: string) => Effect.Effect<void, UploadistaError, never>;
2675
- }, never, UploadFileDataStores | UploadFileKVStore | UploadEventEmitter | GenerateId>;
3324
+ }, never, GenerateId | UploadFileDataStores | UploadFileKVStore | UploadEventEmitter>;
2676
3325
  /**
2677
3326
  * Pre-built UploadServer Effect Layer.
2678
3327
  *
@@ -2703,7 +3352,7 @@ declare function createUploadServer(): Effect.Effect<{
2703
3352
  * }).pipe(Effect.provide(fullUploadSystem));
2704
3353
  * ```
2705
3354
  */
2706
- declare const uploadServer: Layer.Layer<UploadServer, never, UploadFileDataStores | UploadFileKVStore | UploadEventEmitter | GenerateId>;
3355
+ declare const uploadServer: Layer.Layer<UploadServer, never, GenerateId | UploadFileDataStores | UploadFileKVStore | UploadEventEmitter>;
2707
3356
  //#endregion
2708
3357
  //#region src/upload/upload-strategy-negotiator.d.ts
2709
3358
  /**
@@ -2854,16 +3503,6 @@ declare class UploadStrategyNegotiator {
2854
3503
  }
2855
3504
  //#endregion
2856
3505
  //#region src/flow/types/flow-job.d.ts
2857
- /**
2858
- * Flow job tracking and state management types.
2859
- *
2860
- * A FlowJob represents a single execution instance of a flow, tracking its progress,
2861
- * node results, and execution state. Jobs can be paused and resumed, making them
2862
- * suitable for long-running or interactive flows.
2863
- *
2864
- * @module flow/types/flow-job
2865
- * @see {@link FlowServer} for job management operations
2866
- */
2867
3506
  /**
2868
3507
  * Status of an individual node within a flow job.
2869
3508
  *
@@ -2879,11 +3518,20 @@ type FlowJobTaskStatus = "started" | "pending" | "running" | "completed" | "paus
2879
3518
  *
2880
3519
  * @property nodeId - Unique identifier of the node this task represents
2881
3520
  * @property status - Current execution status of the node
2882
- * @property result - Output data from the node (if completed successfully)
3521
+ * @property result - Node execution result data (can be partial data if paused, or complete data if finished)
2883
3522
  * @property error - Error message if the node failed
2884
3523
  * @property retryCount - Number of retry attempts made before success or final failure
2885
3524
  * @property createdAt - When the task was created
2886
3525
  * @property updatedAt - When the task was last updated
3526
+ *
3527
+ * @remarks
3528
+ * The result field can contain:
3529
+ * - Partial/intermediate data when status is "paused" (unknown type)
3530
+ * - Complete data when status is "completed" (could be TypedOutput for output nodes)
3531
+ * - undefined when status is "pending", "running", "started", or "failed"
3532
+ *
3533
+ * For type-safe access to final outputs, use FlowJob.result instead, which contains
3534
+ * the array of TypedOutput from all output nodes.
2887
3535
  */
2888
3536
  type FlowJobTask = {
2889
3537
  nodeId: string;
@@ -2910,7 +3558,7 @@ type FlowJobTask = {
2910
3558
  * @property tasks - Array of node execution tasks
2911
3559
  * @property error - Error message if the job failed
2912
3560
  * @property endedAt - When the job completed or failed
2913
- * @property result - Final output from the flow (only set when completed)
3561
+ * @property result - Array of typed outputs from all output nodes (only set when completed)
2914
3562
  * @property pausedAt - Node ID where execution is paused (if applicable)
2915
3563
  * @property executionState - State needed to resume a paused flow
2916
3564
  * @property intermediateFiles - File IDs to cleanup after completion
@@ -2920,6 +3568,7 @@ type FlowJobTask = {
2920
3568
  * - Paused jobs store execution state and can be resumed with new data
2921
3569
  * - Intermediate files from non-output nodes are automatically cleaned up
2922
3570
  * - Tasks are updated as nodes progress through their lifecycle
3571
+ * - The result field now contains an array of TypedOutput for all output nodes
2923
3572
  *
2924
3573
  * @example
2925
3574
  * ```typescript
@@ -2933,7 +3582,11 @@ type FlowJobTask = {
2933
3582
  * // Poll for status
2934
3583
  * const status = yield* flowServer.getJobStatus(job.id);
2935
3584
  * if (status.status === "completed") {
2936
- * console.log("Final result:", status.result);
3585
+ * // Access typed outputs
3586
+ * console.log("Outputs:", status.result);
3587
+ * for (const output of status.result || []) {
3588
+ * console.log(`${output.nodeId} (${output.nodeType}):`, output.data);
3589
+ * }
2937
3590
  * } else if (status.status === "paused") {
2938
3591
  * // Resume with additional data
2939
3592
  * yield* flowServer.resumeFlow({
@@ -2955,7 +3608,7 @@ type FlowJob = {
2955
3608
  tasks: FlowJobTask[];
2956
3609
  error?: string;
2957
3610
  endedAt?: Date;
2958
- result?: unknown;
3611
+ result?: TypedOutput[];
2959
3612
  pausedAt?: string;
2960
3613
  executionState?: {
2961
3614
  executionOrder: string[];
@@ -3254,8 +3907,8 @@ declare function createFlowServer(): Effect.Effect<{
3254
3907
  cancelFlow: (jobId: string, clientId: string | null) => Effect.Effect<FlowJob, UploadistaError, never>;
3255
3908
  subscribeToFlowEvents: (jobId: string, connection: WebSocketConnection) => Effect.Effect<void, UploadistaError, never>;
3256
3909
  unsubscribeFromFlowEvents: (jobId: string) => Effect.Effect<void, UploadistaError, never>;
3257
- }, never, UploadServer | FlowEventEmitter | FlowJobKVStore | FlowProvider>;
3258
- declare const flowServer: Layer.Layer<FlowServer, never, UploadServer | FlowEventEmitter | FlowJobKVStore | FlowProvider>;
3910
+ }, never, FlowProvider | FlowJobKVStore | FlowEventEmitter | UploadServer>;
3911
+ declare const flowServer: Layer.Layer<FlowServer, never, FlowProvider | FlowJobKVStore | FlowEventEmitter | UploadServer>;
3259
3912
  type FlowServerLayer = typeof flowServer;
3260
3913
  //#endregion
3261
3914
  //#region src/flow/nodes/input-node.d.ts
@@ -3381,7 +4034,7 @@ declare function createInputNode(id: string, params?: InputNodeParams): Effect.E
3381
4034
  };
3382
4035
  } & {
3383
4036
  type: NodeType.input;
3384
- }, never, UploadServer>;
4037
+ }, UploadistaError, UploadServer>;
3385
4038
  //#endregion
3386
4039
  //#region src/flow/nodes/storage-node.d.ts
3387
4040
  /**
@@ -3529,7 +4182,7 @@ declare function createStorageNode(id: string, postProcessFile?: (file: UploadFi
3529
4182
  };
3530
4183
  } & {
3531
4184
  type: NodeType.output;
3532
- }, never, UploadServer>;
4185
+ }, UploadistaError, UploadServer>;
3533
4186
  //#endregion
3534
4187
  //#region src/flow/nodes/transform-node.d.ts
3535
4188
  /**
@@ -3620,7 +4273,7 @@ declare function createTransformNode({
3620
4273
  };
3621
4274
  } & {
3622
4275
  type: NodeType;
3623
- }, never, UploadServer>;
4276
+ }, UploadistaError, UploadServer>;
3624
4277
  //#endregion
3625
4278
  //#region src/flow/parallel-scheduler.d.ts
3626
4279
  /**
@@ -5051,5 +5704,5 @@ type ResolvedUploadMetadata = {
5051
5704
  };
5052
5705
  declare function resolveUploadMetadata(metadata: FileMetadata): ResolvedUploadMetadata;
5053
5706
  //#endregion
5054
- export { LogoTransform as $, BaseKvStore as $n, FlowServerShape as $t, ZipPluginLayer as A, UploadEventEmitter as An, FlowEventFlowError as Ar, ImageAiPluginLayer as At, resizeVideoParamsSchema as B, EventBroadcaster as Bn, FlowEventNodeStart as Br, StorageParams as Bt, DescribeImageParams as C, InputFile as Cn, TypeCompatibilityChecker as Cr, watermarkTransformSchema as Ct, ZipInput as D, EventEmitter as Dn, FlowEvent as Dr, optimizeParamsSchema as Dt, PluginLayer as E, BaseEventEmitterService as En, EventType as Er, OptimizeParams as Et, TrimVideoParams as F, WebSocketMessage as Fn, FlowEventNodeEnd as Fr, ExecutionLevel as Ft, ImagePlugin as G, DataStoreConfig as Gn, createFlowNode as Gr, createInputNode as Gt, extractFrameVideoParamsSchema as H, BufferedUploadFileDataStore as Hn, ConditionOperator as Hr, storageParamsSchema as Ht, trimVideoParamsSchema as I, webSocketMessageSchema as In, FlowEventNodeError as Ir, ParallelScheduler as It, BlurTransform as J, UploadFileDataStores as Jn, FlowProvider as Jt, ImagePluginLayer as K, DataStoreWriteOptions as Kn, getNodeData as Kr, inputDataSchema as Kt, TranscodeVideoParams as L, UploadEvent as Ln, FlowEventNodePause as Lr, ParallelSchedulerConfig as Lt, VideoPlugin as M, flowEventEmitter as Mn, FlowEventFlowStart as Mr, CredentialProvider as Mt, VideoPluginLayer as N, uploadEventEmitter as Nn, FlowEventJobEnd as Nr, CredentialProviderLayer as Nt, ZipParams as O, FlowEventEmitter as On, FlowEventFlowCancel as Or, ImageAiContext as Ot, VideoPluginShape as P, WebSocketConnection as Pn, FlowEventJobStart as Pr, CredentialProviderShape as Pt, GrayscaleTransform as Q, isDataStore as Qn, FlowServerOptions as Qt, transcodeVideoParamsSchema as R, UploadEventType as Rn, FlowEventNodeResponse as Rr, TransformNodeConfig as Rt, removeBackgroundParamsSchema as S, MiddlewareServiceLive as Sn, NodeTypeMap as Sr, transformationSchema as St, Plugin as T, BaseEventEmitter as Tn, waitingNodeExecution as Tr, resizeParamsSchema as Tt, DescribeVideoMetadata as U, DataStore as Un, ConditionValue as Ur, InputData as Ut, ExtractFrameVideoParams as V, EventBroadcasterService as Vn, ConditionField as Vr, createStorageNode as Vt, describeVideoMetadataSchema as W, DataStoreCapabilities as Wn, NodeType as Wr, InputNodeParams as Wt, ContrastTransform as X, UploadStrategy as Xn, FlowServer as Xt, BrightnessTransform as Y, UploadFileDataStoresShape as Yn, FlowProviderShape as Yt, FlipTransform as Z, createDataStoreLayer as Zn, FlowServerLayer as Zt, ExtractLayerService as _, detectMimeType as _n, FlowConfig as _r, rotateTransformSchema as _t, FlowInputMap as a, FlowJobStatus as an, flowJobKvStore as ar, TextTransform as at, FlowCondition as b, MiddlewareNext as bn, NodeConnectionValidator as br, textTransformSchema as bt, FlowRequirements as c, NegotiatedStrategy as cn, UploadFile as cr, TransformationType as ct, TypedFlow as d, UploadServer as dn, FlowData as dr, brightnessTransformSchema as dt, FlowWaitUntil as en, BaseKvStoreService as er, OverlayPosition as et, TypedFlowConfig as f, UploadServerOptions as fn, FlowExecutionResult as fr, contrastTransformSchema as ft, ExtractEffectRequirements as g, compareMimeTypes as gn, createFlowEdge as gr, resizeTransformSchema as gt, ExtractEffectError as h, uploadServer as hn, FlowEdge as hr, logoTransformSchema as ht, runArgsSchema as i, FlowJob as in, UploadFileKVStore as ir, SharpenTransform as it, ZipPluginShape as j, eventToMessageSerializer as jn, FlowEventFlowPause as jr, ImageAiPluginShape as jt, ZipPlugin as k, TypedEventEmitter as kn, FlowEventFlowEnd as kr, ImageAiPlugin as kt, NodeDefinition as l, UploadStrategyNegotiator as ln, uploadFileSchema as lr, WatermarkTransform as lt, createFlow as m, createUploadServer as mn, getFlowData as mr, grayscaleTransformSchema as mt, resolveUploadMetadata as n, createFlowServer as nn, KvStore as nr, RotateTransform as nt, FlowOutputMap as o, FlowJobTask as on, jsonSerializer as or, TransformImageParams as ot, TypedFlowEdge as p, UploadServerShape as pn, createFlowWithSchema as pr, flipTransformSchema as pt, ImagePluginShape as q, UploadFileDataStore as qn, inputNodeParamsSchema as qt, RunArgs as r, flowServer as rn, TypedKvStore as rr, SepiaTransform as rt, FlowPluginRequirements as s, FlowJobTaskStatus as sn, uploadFileKvStore as sr, Transformation as st, ResolvedUploadMetadata as t, WaitUntilCallback as tn, FlowJobKVStore as tr, ResizeTransform as tt, NodeDefinitionsRecord as u, UploadStrategyOptions as un, Flow as ur, blurTransformSchema as ut, ExtractLayerServices as v, Middleware as vn, FlowNode as vr, sepiaTransformSchema as vt, describeImageParamsSchema as w, inputFileSchema as wn, completeNodeExecution as wr, ResizeParams as wt, RemoveBackgroundParams as x, MiddlewareService as xn, NodeExecutionResult as xr, transformImageParamsSchema as xt, ResolveEffect as y, MiddlewareContext as yn, FlowNodeData as yr, sharpenTransformSchema as yt, ResizeVideoParams as z, uploadEventSchema as zn, FlowEventNodeResume as zr, createTransformNode as zt };
5055
- //# sourceMappingURL=index-4VDJDcWM.d.cts.map
5707
+ export { LogoTransform as $, DataStoreWriteOptions as $n, FlowEventNodeResponse as $r, FlowServerShape as $t, ZipPluginLayer as A, InputFile as An, CustomTypedOutput as Ar, ImageAiPluginLayer as At, resizeVideoParamsSchema as B, uploadEventEmitter as Bn, waitingNodeExecution as Br, StorageParams as Bt, DescribeImageParams as C, hasOutputOfType as Cn, FlowData as Cr, watermarkTransformSchema as Ct, ZipInput as D, MiddlewareNext as Dn, FlowEdge as Dr, optimizeParamsSchema as Dt, PluginLayer as E, MiddlewareContext as En, getFlowData as Er, OptimizeParams as Et, TrimVideoParams as F, FlowEventEmitter as Fn, NodeExecutionResult as Fr, ExecutionLevel as Ft, ImagePlugin as G, UploadEventType as Gn, FlowEventFlowError as Gr, createInputNode as Gt, extractFrameVideoParamsSchema as H, WebSocketMessage as Hn, FlowEvent as Hr, storageParamsSchema as Ht, trimVideoParamsSchema as I, TypedEventEmitter as In, NodeTypeMap as Ir, ParallelScheduler as It, BlurTransform as J, EventBroadcasterService as Jn, FlowEventJobEnd as Jr, FlowProvider as Jt, ImagePluginLayer as K, uploadEventSchema as Kn, FlowEventFlowPause as Kr, inputDataSchema as Kt, TranscodeVideoParams as L, UploadEventEmitter as Ln, TypeCompatibilityChecker as Lr, ParallelSchedulerConfig as Lt, VideoPlugin as M, BaseEventEmitter as Mn, FlowNode as Mr, CredentialProvider as Mt, VideoPluginLayer as N, BaseEventEmitterService as Nn, FlowNodeData as Nr, CredentialProviderLayer as Nt, ZipParams as O, MiddlewareService as On, createFlowEdge as Or, ImageAiContext as Ot, VideoPluginShape as P, EventEmitter as Pn, NodeConnectionValidator as Pr, CredentialProviderShape as Pt, GrayscaleTransform as Q, DataStoreConfig as Qn, FlowEventNodePause as Qr, FlowServerOptions as Qt, transcodeVideoParamsSchema as R, eventToMessageSerializer as Rn, TypedOutput as Rr, TransformNodeConfig as Rt, removeBackgroundParamsSchema as S, getSingleOutputByType as Sn, Flow as Sr, transformationSchema as St, Plugin as T, Middleware as Tn, createFlowWithSchema as Tr, resizeParamsSchema as Tt, DescribeVideoMetadata as U, webSocketMessageSchema as Un, FlowEventFlowCancel as Ur, InputData as Ut, ExtractFrameVideoParams as V, WebSocketConnection as Vn, EventType as Vr, createStorageNode as Vt, describeVideoMetadataSchema as W, UploadEvent as Wn, FlowEventFlowEnd as Wr, InputNodeParams as Wt, ContrastTransform as X, DataStore as Xn, FlowEventNodeEnd as Xr, FlowServer as Xt, BrightnessTransform as Y, BufferedUploadFileDataStore as Yn, FlowEventJobStart as Yr, FlowProviderShape as Yt, FlipTransform as Z, DataStoreCapabilities as Zn, FlowEventNodeError as Zr, FlowServerLayer as Zt, ExtractLayerService as _, detectMimeType as _n, FlowTypeRegistry as _r, rotateTransformSchema as _t, FlowInputMap as a, NodeType as ai, FlowJobStatus as an, isDataStore as ar, TextTransform as at, FlowCondition as b, getFirstOutputByType as bn, ValidationResult as br, textTransformSchema as bt, FlowRequirements as c, UploadFile as ci, NegotiatedStrategy as cn, FlowJobKVStore as cr, TransformationType as ct, TypedFlow as d, UploadServer as dn, UploadFileKVStore as dr, brightnessTransformSchema as dt, FlowEventNodeResume as ei, FlowWaitUntil as en, UploadFileDataStore as er, OverlayPosition as et, TypedFlowConfig as f, UploadServerOptions as fn, flowJobKvStore as fr, contrastTransformSchema as ft, ExtractEffectRequirements as g, compareMimeTypes as gn, STREAMING_INPUT_TYPE_ID as gr, resizeTransformSchema as gt, ExtractEffectError as h, uploadServer as hn, STORAGE_OUTPUT_TYPE_ID as hr, logoTransformSchema as ht, runArgsSchema as i, ConditionValue as ii, FlowJob as in, createDataStoreLayer as ir, SharpenTransform as it, ZipPluginShape as j, inputFileSchema as jn, FlowConfig as jr, ImageAiPluginShape as jt, ZipPlugin as k, MiddlewareServiceLive as kn, BuiltInTypedOutput as kr, ImageAiPlugin as kt, NodeDefinition as l, uploadFileSchema as li, UploadStrategyNegotiator as ln, KvStore as lr, WatermarkTransform as lt, createFlow as m, createUploadServer as mn, uploadFileKvStore as mr, grayscaleTransformSchema as mt, resolveUploadMetadata as n, ConditionField as ni, createFlowServer as nn, UploadFileDataStoresShape as nr, RotateTransform as nt, FlowOutputMap as o, createFlowNode as oi, FlowJobTask as on, BaseKvStore as or, TransformImageParams as ot, TypedFlowEdge as p, UploadServerShape as pn, jsonSerializer as pr, flipTransformSchema as pt, ImagePluginShape as q, EventBroadcaster as qn, FlowEventFlowStart as qr, inputNodeParamsSchema as qt, RunArgs as r, ConditionOperator as ri, flowServer as rn, UploadStrategy as rr, SepiaTransform as rt, FlowPluginRequirements as s, getNodeData as si, FlowJobTaskStatus as sn, BaseKvStoreService as sr, Transformation as st, ResolvedUploadMetadata as t, FlowEventNodeStart as ti, WaitUntilCallback as tn, UploadFileDataStores as tr, ResizeTransform as tt, NodeDefinitionsRecord as u, UploadStrategyOptions as un, TypedKvStore as ur, blurTransformSchema as ut, ExtractLayerServices as v, createTypeGuard as vn, NodeTypeCategory as vr, sepiaTransformSchema as vt, describeImageParamsSchema as w, isStorageOutput as wn, FlowExecutionResult as wr, ResizeParams as wt, RemoveBackgroundParams as x, getOutputByNodeId as xn, flowTypeRegistry as xr, transformImageParamsSchema as xt, ResolveEffect as y, filterOutputsByType as yn, NodeTypeDefinition as yr, sharpenTransformSchema as yt, ResizeVideoParams as z, flowEventEmitter as zn, completeNodeExecution as zr, createTransformNode as zt };
5708
+ //# sourceMappingURL=index-5K4oXy67.d.cts.map