@uploadista/core 0.0.2 → 0.0.3
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/.turbo/turbo-build.log +88 -1
- package/README.md +6 -20
- package/dist/checksum-C5qE-5eg.js +2 -0
- package/dist/checksum-C5qE-5eg.js.map +1 -0
- package/dist/checksum-wSBuXX84.cjs +1 -0
- package/dist/errors/index.cjs +1 -1
- package/dist/errors/index.d.cts +2 -2
- package/dist/errors/index.d.ts +3 -3
- package/dist/errors/index.js +1 -2
- package/dist/flow/index.cjs +1 -1
- package/dist/flow/index.d.cts +5 -5
- package/dist/flow/index.d.ts +6 -24
- package/dist/flow/index.js +1 -24
- package/dist/flow-B0mMJM5Y.js +2 -0
- package/dist/flow-B0mMJM5Y.js.map +1 -0
- package/dist/flow-s5bgJsdb.cjs +1 -0
- package/dist/index-0xq1cArb.d.cts +4132 -0
- package/dist/index-0xq1cArb.d.cts.map +1 -0
- package/dist/index-B46hb7yB.d.cts +36 -0
- package/dist/index-B46hb7yB.d.cts.map +1 -0
- package/dist/{streams/stream-limiter.d.cts → index-C1mxuUxK.d.ts} +3 -3
- package/dist/index-C1mxuUxK.d.ts.map +1 -0
- package/dist/index-DMJv8Tvo.d.ts +168 -0
- package/dist/index-DMJv8Tvo.d.ts.map +1 -0
- package/dist/index-GLPiXqj4.d.cts +168 -0
- package/dist/index-GLPiXqj4.d.cts.map +1 -0
- package/dist/index-YegzC0p1.d.ts +3952 -0
- package/dist/index-YegzC0p1.d.ts.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +6 -5
- package/dist/index.js +1 -5
- package/dist/stream-limiter-CTuiXkcq.js +2 -0
- package/dist/{stream-limiter-CoWKv39w.js.map → stream-limiter-CTuiXkcq.js.map} +1 -1
- package/dist/stream-limiter-DYGG4t9f.cjs +1 -0
- package/dist/streams/index.cjs +1 -0
- package/dist/streams/index.d.cts +3 -0
- package/dist/streams/index.d.ts +3 -0
- package/dist/streams/index.js +1 -0
- package/dist/streams-BiD_pOPH.cjs +0 -0
- package/dist/streams-Bs3GDNKJ.js +1 -0
- package/dist/types/index.cjs +1 -1
- package/dist/types/index.d.cts +5 -5
- package/dist/types/index.d.ts +6 -10
- package/dist/types/index.js +1 -9
- package/dist/types-Dj9g8ocl.cjs +1 -0
- package/dist/types-m26wrG-Z.js +2 -0
- package/dist/types-m26wrG-Z.js.map +1 -0
- package/dist/upload/index.cjs +1 -1
- package/dist/upload/index.d.cts +5 -5
- package/dist/upload/index.d.ts +6 -4
- package/dist/upload/index.js +1 -3
- package/dist/upload-BzU7ifyH.js +2 -0
- package/dist/upload-BzU7ifyH.js.map +1 -0
- package/dist/upload-DvLp6TXO.cjs +1 -0
- package/dist/uploadista-error-CAtkQiAv.d.cts +221 -0
- package/dist/uploadista-error-CAtkQiAv.d.cts.map +1 -0
- package/dist/{uploadista-error-BVsVxqvz.js → uploadista-error-CjfcFnVa.js} +9 -2
- package/dist/uploadista-error-CjfcFnVa.js.map +1 -0
- package/dist/uploadista-error-D9SONF9K.d.ts +221 -0
- package/dist/uploadista-error-D9SONF9K.d.ts.map +1 -0
- package/dist/{uploadista-error-BB-Wdiz9.cjs → uploadista-error-DdTP-Rjx.cjs} +9 -2
- package/dist/utils/index.cjs +1 -0
- package/dist/utils/index.d.cts +3 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils-BILytQlb.js +2 -0
- package/dist/utils-BILytQlb.js.map +1 -0
- package/dist/utils-BLsIUd8c.cjs +1 -0
- package/package.json +12 -16
- package/src/index.ts +2 -0
- package/src/streams/index.ts +1 -0
- package/src/utils/index.ts +5 -0
- package/tsdown.config.ts +2 -10
- package/dist/chunk-CUT6urMc.cjs +0 -1
- package/dist/debounce-C2SeqcxD.js +0 -2
- package/dist/debounce-C2SeqcxD.js.map +0 -1
- package/dist/debounce-LZK7yS7Z.cjs +0 -1
- package/dist/errors/index.d.ts.map +0 -1
- package/dist/errors/uploadista-error.d.ts +0 -209
- package/dist/errors/uploadista-error.d.ts.map +0 -1
- package/dist/errors/uploadista-error.js +0 -322
- package/dist/flow/edge.d.ts +0 -47
- package/dist/flow/edge.d.ts.map +0 -1
- package/dist/flow/edge.js +0 -40
- package/dist/flow/event.d.ts +0 -206
- package/dist/flow/event.d.ts.map +0 -1
- package/dist/flow/event.js +0 -53
- package/dist/flow/flow-server.d.ts +0 -223
- package/dist/flow/flow-server.d.ts.map +0 -1
- package/dist/flow/flow-server.js +0 -614
- package/dist/flow/flow.d.ts +0 -238
- package/dist/flow/flow.d.ts.map +0 -1
- package/dist/flow/flow.js +0 -629
- package/dist/flow/index.d.ts.map +0 -1
- package/dist/flow/node.d.ts +0 -136
- package/dist/flow/node.d.ts.map +0 -1
- package/dist/flow/node.js +0 -153
- package/dist/flow/nodes/index.d.ts +0 -8
- package/dist/flow/nodes/index.d.ts.map +0 -1
- package/dist/flow/nodes/index.js +0 -7
- package/dist/flow/nodes/input-node.d.ts +0 -78
- package/dist/flow/nodes/input-node.d.ts.map +0 -1
- package/dist/flow/nodes/input-node.js +0 -233
- package/dist/flow/nodes/storage-node.d.ts +0 -67
- package/dist/flow/nodes/storage-node.d.ts.map +0 -1
- package/dist/flow/nodes/storage-node.js +0 -94
- package/dist/flow/nodes/streaming-input-node.d.ts +0 -69
- package/dist/flow/nodes/streaming-input-node.d.ts.map +0 -1
- package/dist/flow/nodes/streaming-input-node.js +0 -156
- package/dist/flow/nodes/transform-node.d.ts +0 -85
- package/dist/flow/nodes/transform-node.d.ts.map +0 -1
- package/dist/flow/nodes/transform-node.js +0 -107
- package/dist/flow/parallel-scheduler.d.ts +0 -175
- package/dist/flow/parallel-scheduler.d.ts.map +0 -1
- package/dist/flow/parallel-scheduler.js +0 -193
- package/dist/flow/plugins/credential-provider.d.ts +0 -47
- package/dist/flow/plugins/credential-provider.d.ts.map +0 -1
- package/dist/flow/plugins/credential-provider.js +0 -24
- package/dist/flow/plugins/image-ai-plugin.d.ts +0 -61
- package/dist/flow/plugins/image-ai-plugin.d.ts.map +0 -1
- package/dist/flow/plugins/image-ai-plugin.js +0 -21
- package/dist/flow/plugins/image-plugin.d.ts +0 -52
- package/dist/flow/plugins/image-plugin.d.ts.map +0 -1
- package/dist/flow/plugins/image-plugin.js +0 -22
- package/dist/flow/plugins/types/describe-image-node.d.ts +0 -16
- package/dist/flow/plugins/types/describe-image-node.d.ts.map +0 -1
- package/dist/flow/plugins/types/describe-image-node.js +0 -9
- package/dist/flow/plugins/types/index.d.ts +0 -9
- package/dist/flow/plugins/types/index.d.ts.map +0 -1
- package/dist/flow/plugins/types/index.js +0 -8
- package/dist/flow/plugins/types/optimize-node.d.ts +0 -20
- package/dist/flow/plugins/types/optimize-node.d.ts.map +0 -1
- package/dist/flow/plugins/types/optimize-node.js +0 -11
- package/dist/flow/plugins/types/remove-background-node.d.ts +0 -16
- package/dist/flow/plugins/types/remove-background-node.d.ts.map +0 -1
- package/dist/flow/plugins/types/remove-background-node.js +0 -9
- package/dist/flow/plugins/types/resize-node.d.ts +0 -21
- package/dist/flow/plugins/types/resize-node.d.ts.map +0 -1
- package/dist/flow/plugins/types/resize-node.js +0 -16
- package/dist/flow/plugins/zip-plugin.d.ts +0 -62
- package/dist/flow/plugins/zip-plugin.d.ts.map +0 -1
- package/dist/flow/plugins/zip-plugin.js +0 -21
- package/dist/flow/typed-flow.d.ts +0 -90
- package/dist/flow/typed-flow.d.ts.map +0 -1
- package/dist/flow/typed-flow.js +0 -59
- package/dist/flow/types/flow-file.d.ts +0 -45
- package/dist/flow/types/flow-file.d.ts.map +0 -1
- package/dist/flow/types/flow-file.js +0 -27
- package/dist/flow/types/flow-job.d.ts +0 -118
- package/dist/flow/types/flow-job.d.ts.map +0 -1
- package/dist/flow/types/flow-job.js +0 -11
- package/dist/flow/types/flow-types.d.ts +0 -321
- package/dist/flow/types/flow-types.d.ts.map +0 -1
- package/dist/flow/types/flow-types.js +0 -52
- package/dist/flow/types/index.d.ts +0 -4
- package/dist/flow/types/index.d.ts.map +0 -1
- package/dist/flow/types/index.js +0 -3
- package/dist/flow/types/run-args.d.ts +0 -38
- package/dist/flow/types/run-args.d.ts.map +0 -1
- package/dist/flow/types/run-args.js +0 -30
- package/dist/flow/types/type-validator.d.ts +0 -26
- package/dist/flow/types/type-validator.d.ts.map +0 -1
- package/dist/flow/types/type-validator.js +0 -134
- package/dist/flow/utils/resolve-upload-metadata.d.ts +0 -11
- package/dist/flow/utils/resolve-upload-metadata.d.ts.map +0 -1
- package/dist/flow/utils/resolve-upload-metadata.js +0 -28
- package/dist/flow-2zXnEiWL.cjs +0 -1
- package/dist/flow-CRaKy7Vj.js +0 -2
- package/dist/flow-CRaKy7Vj.js.map +0 -1
- package/dist/generate-id-Dm-Vboxq.d.ts +0 -34
- package/dist/generate-id-Dm-Vboxq.d.ts.map +0 -1
- package/dist/generate-id-LjJRLD6N.d.cts +0 -34
- package/dist/generate-id-LjJRLD6N.d.cts.map +0 -1
- package/dist/generate-id-xHp_Z7Cl.cjs +0 -1
- package/dist/generate-id-yohS1ZDk.js +0 -2
- package/dist/generate-id-yohS1ZDk.js.map +0 -1
- package/dist/index-BO8GZlbD.d.cts +0 -1040
- package/dist/index-BO8GZlbD.d.cts.map +0 -1
- package/dist/index-D-CoVpkZ.d.ts +0 -1004
- package/dist/index-D-CoVpkZ.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/logger/logger.cjs +0 -1
- package/dist/logger/logger.d.cts +0 -8
- package/dist/logger/logger.d.cts.map +0 -1
- package/dist/logger/logger.d.ts +0 -5
- package/dist/logger/logger.d.ts.map +0 -1
- package/dist/logger/logger.js +0 -10
- package/dist/logger/logger.js.map +0 -1
- package/dist/semaphore-0ZwjVpyF.js +0 -2
- package/dist/semaphore-0ZwjVpyF.js.map +0 -1
- package/dist/semaphore-BHprIjFI.d.cts +0 -37
- package/dist/semaphore-BHprIjFI.d.cts.map +0 -1
- package/dist/semaphore-DThupBkc.d.ts +0 -37
- package/dist/semaphore-DThupBkc.d.ts.map +0 -1
- package/dist/semaphore-DVrONiAV.cjs +0 -1
- package/dist/stream-limiter-CoWKv39w.js +0 -2
- package/dist/stream-limiter-JgOwmkMa.cjs +0 -1
- package/dist/streams/multi-stream.cjs +0 -1
- package/dist/streams/multi-stream.d.cts +0 -91
- package/dist/streams/multi-stream.d.cts.map +0 -1
- package/dist/streams/multi-stream.d.ts +0 -86
- package/dist/streams/multi-stream.d.ts.map +0 -1
- package/dist/streams/multi-stream.js +0 -149
- package/dist/streams/multi-stream.js.map +0 -1
- package/dist/streams/stream-limiter.cjs +0 -1
- package/dist/streams/stream-limiter.d.cts.map +0 -1
- package/dist/streams/stream-limiter.d.ts +0 -27
- package/dist/streams/stream-limiter.d.ts.map +0 -1
- package/dist/streams/stream-limiter.js +0 -49
- package/dist/streams/stream-splitter.cjs +0 -1
- package/dist/streams/stream-splitter.d.cts +0 -68
- package/dist/streams/stream-splitter.d.cts.map +0 -1
- package/dist/streams/stream-splitter.d.ts +0 -51
- package/dist/streams/stream-splitter.d.ts.map +0 -1
- package/dist/streams/stream-splitter.js +0 -175
- package/dist/streams/stream-splitter.js.map +0 -1
- package/dist/types/data-store-registry.d.ts +0 -13
- package/dist/types/data-store-registry.d.ts.map +0 -1
- package/dist/types/data-store-registry.js +0 -4
- package/dist/types/data-store.d.ts +0 -316
- package/dist/types/data-store.d.ts.map +0 -1
- package/dist/types/data-store.js +0 -157
- package/dist/types/event-broadcaster.d.ts +0 -28
- package/dist/types/event-broadcaster.d.ts.map +0 -1
- package/dist/types/event-broadcaster.js +0 -6
- package/dist/types/event-emitter.d.ts +0 -378
- package/dist/types/event-emitter.d.ts.map +0 -1
- package/dist/types/event-emitter.js +0 -223
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/input-file.d.ts +0 -104
- package/dist/types/input-file.d.ts.map +0 -1
- package/dist/types/input-file.js +0 -27
- package/dist/types/kv-store.d.ts +0 -281
- package/dist/types/kv-store.d.ts.map +0 -1
- package/dist/types/kv-store.js +0 -234
- package/dist/types/middleware.d.ts +0 -17
- package/dist/types/middleware.d.ts.map +0 -1
- package/dist/types/middleware.js +0 -21
- package/dist/types/upload-event.d.ts +0 -105
- package/dist/types/upload-event.d.ts.map +0 -1
- package/dist/types/upload-event.js +0 -71
- package/dist/types/upload-file.d.ts +0 -136
- package/dist/types/upload-file.d.ts.map +0 -1
- package/dist/types/upload-file.js +0 -34
- package/dist/types/websocket.d.ts +0 -144
- package/dist/types/websocket.d.ts.map +0 -1
- package/dist/types/websocket.js +0 -40
- package/dist/types-BT-cvi7T.cjs +0 -1
- package/dist/types-DhU2j-XF.js +0 -2
- package/dist/types-DhU2j-XF.js.map +0 -1
- package/dist/upload/convert-to-stream.d.ts +0 -38
- package/dist/upload/convert-to-stream.d.ts.map +0 -1
- package/dist/upload/convert-to-stream.js +0 -43
- package/dist/upload/convert-upload-to-flow-file.d.ts +0 -14
- package/dist/upload/convert-upload-to-flow-file.d.ts.map +0 -1
- package/dist/upload/convert-upload-to-flow-file.js +0 -21
- package/dist/upload/create-upload.d.ts +0 -68
- package/dist/upload/create-upload.d.ts.map +0 -1
- package/dist/upload/create-upload.js +0 -157
- package/dist/upload/index.d.ts.map +0 -1
- package/dist/upload/mime.d.ts +0 -24
- package/dist/upload/mime.d.ts.map +0 -1
- package/dist/upload/mime.js +0 -351
- package/dist/upload/upload-chunk.d.ts +0 -58
- package/dist/upload/upload-chunk.d.ts.map +0 -1
- package/dist/upload/upload-chunk.js +0 -277
- package/dist/upload/upload-server.d.ts +0 -221
- package/dist/upload/upload-server.d.ts.map +0 -1
- package/dist/upload/upload-server.js +0 -181
- package/dist/upload/upload-strategy-negotiator.d.ts +0 -148
- package/dist/upload/upload-strategy-negotiator.d.ts.map +0 -1
- package/dist/upload/upload-strategy-negotiator.js +0 -217
- package/dist/upload/upload-url.d.ts +0 -68
- package/dist/upload/upload-url.d.ts.map +0 -1
- package/dist/upload/upload-url.js +0 -142
- package/dist/upload/write-to-store.d.ts +0 -77
- package/dist/upload/write-to-store.d.ts.map +0 -1
- package/dist/upload/write-to-store.js +0 -147
- package/dist/upload-DLuICjpP.cjs +0 -1
- package/dist/upload-DaXO34dE.js +0 -2
- package/dist/upload-DaXO34dE.js.map +0 -1
- package/dist/uploadista-error-BVsVxqvz.js.map +0 -1
- package/dist/uploadista-error-CwxYs4EB.d.ts +0 -52
- package/dist/uploadista-error-CwxYs4EB.d.ts.map +0 -1
- package/dist/uploadista-error-kKlhLRhY.d.cts +0 -52
- package/dist/uploadista-error-kKlhLRhY.d.cts.map +0 -1
- package/dist/utils/checksum.d.ts +0 -22
- package/dist/utils/checksum.d.ts.map +0 -1
- package/dist/utils/checksum.js +0 -49
- package/dist/utils/debounce.cjs +0 -1
- package/dist/utils/debounce.d.cts +0 -38
- package/dist/utils/debounce.d.cts.map +0 -1
- package/dist/utils/debounce.d.ts +0 -36
- package/dist/utils/debounce.d.ts.map +0 -1
- package/dist/utils/debounce.js +0 -73
- package/dist/utils/generate-id.cjs +0 -1
- package/dist/utils/generate-id.d.cts +0 -2
- package/dist/utils/generate-id.d.ts +0 -32
- package/dist/utils/generate-id.d.ts.map +0 -1
- package/dist/utils/generate-id.js +0 -23
- package/dist/utils/md5.cjs +0 -1
- package/dist/utils/md5.d.cts +0 -73
- package/dist/utils/md5.d.cts.map +0 -1
- package/dist/utils/md5.d.ts +0 -71
- package/dist/utils/md5.d.ts.map +0 -1
- package/dist/utils/md5.js +0 -417
- package/dist/utils/md5.js.map +0 -1
- package/dist/utils/once.cjs +0 -1
- package/dist/utils/once.d.cts +0 -25
- package/dist/utils/once.d.cts.map +0 -1
- package/dist/utils/once.d.ts +0 -21
- package/dist/utils/once.d.ts.map +0 -1
- package/dist/utils/once.js +0 -54
- package/dist/utils/once.js.map +0 -1
- package/dist/utils/semaphore.cjs +0 -1
- package/dist/utils/semaphore.d.cts +0 -3
- package/dist/utils/semaphore.d.ts +0 -78
- package/dist/utils/semaphore.d.ts.map +0 -1
- package/dist/utils/semaphore.js +0 -134
- package/dist/utils/throttle.cjs +0 -1
- package/dist/utils/throttle.d.cts +0 -24
- package/dist/utils/throttle.d.cts.map +0 -1
- package/dist/utils/throttle.d.ts +0 -18
- package/dist/utils/throttle.d.ts.map +0 -1
- package/dist/utils/throttle.js +0 -20
- package/dist/utils/throttle.js.map +0 -1
- package/src/logger/logger.ts +0 -14
- /package/dist/{errors-CRm1FHHT.cjs → errors-D-K-vxsP.cjs} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flow-B0mMJM5Y.js","names":["graph: Record<string, string[]>","inDegree: Record<string, number>","levels: ExecutionLevel[]","defaultTypeChecker: TypeCompatibilityChecker","errors: string[]","expectedSchemas: Record<string, unknown>","actualSchemas: Record<string, unknown>","resolvedNodes: Array<FlowNode<any, any, UploadistaError>>","graph: Record<string, string[]>","inDegree: Record<string, number>","reverseGraph: Record<string, string[]>","queue: string[]","result: string[]","inputs: Record<string, unknown>","mappedInputs: Record<string, z.infer<TFlowInputSchema>>","flowOutputs: Record<string, unknown>","lastError: UploadistaError | null","nodeInput: unknown","nodeInputsForExecution: Record<string, unknown>","result","executionOrder: string[]","nodeResults: Map<string, unknown>","startIndex: number","job: FlowJob","inputFile: InputFile","flowEdges: FlowEdge[]"],"sources":["../src/flow/edge.ts","../src/flow/event.ts","../src/flow/node.ts","../src/flow/parallel-scheduler.ts","../src/flow/types/type-validator.ts","../src/flow/flow.ts","../src/flow/flow-server.ts","../src/flow/types/flow-types.ts","../src/flow/utils/resolve-upload-metadata.ts","../src/flow/nodes/input-node.ts","../src/flow/nodes/storage-node.ts","../src/flow/nodes/transform-node.ts","../src/flow/plugins/credential-provider.ts","../src/flow/plugins/image-ai-plugin.ts","../src/flow/plugins/image-plugin.ts","../src/flow/plugins/types/describe-image-node.ts","../src/flow/plugins/types/optimize-node.ts","../src/flow/plugins/types/remove-background-node.ts","../src/flow/plugins/types/resize-node.ts","../src/flow/plugins/zip-plugin.ts","../src/flow/typed-flow.ts","../src/flow/types/run-args.ts"],"sourcesContent":["import type { FlowEdge as EnhancedFlowEdge } from \"./types/flow-types\";\n\n/**\n * Represents a connection between two nodes in a flow, defining the data flow direction.\n *\n * Edges connect the output of a source node to the input of a target node,\n * enabling data to flow through the processing pipeline in a directed acyclic graph (DAG).\n */\nexport type FlowEdge = EnhancedFlowEdge;\n\n/**\n * Creates a flow edge connecting two nodes in a processing pipeline.\n *\n * Edges define how data flows between nodes. The data output from the source node\n * becomes the input for the target node. For nodes with multiple inputs/outputs,\n * ports can be specified to route data to specific connections.\n *\n * @param config - Edge configuration\n * @param config.source - ID of the source node (data originates here)\n * @param config.target - ID of the target node (data flows to here)\n * @param config.sourcePort - Optional port name on the source node for multi-output nodes\n * @param config.targetPort - Optional port name on the target node for multi-input nodes\n *\n * @returns A FlowEdge object representing the connection\n *\n * @example\n * ```typescript\n * // Simple edge connecting two nodes\n * const edge = createFlowEdge({\n * source: \"input-1\",\n * target: \"process-1\"\n * });\n *\n * // Edge with ports for multi-input/output nodes\n * const portEdge = createFlowEdge({\n * source: \"multiplex-1\",\n * target: \"merge-1\",\n * sourcePort: \"out-a\",\n * targetPort: \"in-1\"\n * });\n * ```\n */\nexport function createFlowEdge({\n source,\n target,\n sourcePort,\n targetPort,\n}: {\n source: string;\n target: string;\n sourcePort?: string;\n targetPort?: string;\n}): FlowEdge {\n return {\n source,\n target,\n sourcePort,\n targetPort,\n };\n}\n","/**\n * Flow execution event types and definitions.\n *\n * This module defines the event system used to monitor and track flow execution.\n * Events are emitted at various stages of flow and node execution, allowing\n * real-time monitoring, logging, and WebSocket updates to clients.\n *\n * @module flow/event\n * @see {@link FlowEvent} for the union of all event types\n */\n\nimport type { NodeType } from \"./node\";\n\n/**\n * Enumeration of all possible flow and node execution event types.\n *\n * Events follow a lifecycle pattern:\n * - Job level: JobStart → ... → JobEnd\n * - Flow level: FlowStart → ... → (FlowEnd | FlowError)\n * - Node level: NodeStart → ... → (NodeEnd | NodePause | NodeError)\n *\n * @example\n * ```typescript\n * // Listen for flow completion\n * if (event.eventType === EventType.FlowEnd) {\n * console.log(\"Flow completed:\", event.result);\n * }\n * ```\n */\nexport enum EventType {\n /** Emitted when a job starts execution */\n JobStart = \"job-start\",\n /** Emitted when a job completes (success or failure) */\n JobEnd = \"job-end\",\n /** Emitted when a flow begins execution */\n FlowStart = \"flow-start\",\n /** Emitted when a flow completes successfully */\n FlowEnd = \"flow-end\",\n /** Emitted when a flow encounters an error */\n FlowError = \"flow-error\",\n /** Emitted when a node starts processing */\n NodeStart = \"node-start\",\n /** Emitted when a node completes successfully */\n NodeEnd = \"node-end\",\n /** Emitted when a node pauses (waiting for additional data) */\n NodePause = \"node-pause\",\n /** Emitted when a paused node resumes execution */\n NodeResume = \"node-resume\",\n /** Emitted when a node encounters an error */\n NodeError = \"node-error\",\n /** Emitted for streaming node data (e.g., progress updates) */\n NodeStream = \"node-stream\",\n /** Emitted for node response data */\n NodeResponse = \"node-response\",\n}\n\n/**\n * Event emitted when a job starts execution.\n */\nexport type FlowEventJobStart = {\n jobId: string;\n eventType: EventType.JobStart;\n};\n\n/**\n * Event emitted when a job completes (either successfully or with failure).\n */\nexport type FlowEventJobEnd = {\n jobId: string;\n eventType: EventType.JobEnd;\n};\n\n/**\n * Event emitted when a flow begins execution.\n * This is the first event after JobStart in the execution lifecycle.\n */\nexport type FlowEventFlowStart = {\n jobId: string;\n flowId: string;\n eventType: EventType.FlowStart;\n};\n\n/**\n * Event emitted when a flow completes successfully.\n *\n * @property result - The final output from all output nodes in the flow\n */\nexport type FlowEventFlowEnd = {\n jobId: string;\n flowId: string;\n eventType: EventType.FlowEnd;\n result?: unknown; // Flow execution result\n};\n\n/**\n * Event emitted when a flow encounters an unrecoverable error.\n *\n * @property error - Error message describing what went wrong\n */\nexport type FlowEventFlowError = {\n jobId: string;\n flowId: string;\n eventType: EventType.FlowError;\n error: string;\n};\n\n/**\n * Event emitted when a node begins processing.\n *\n * @property nodeName - Human-readable node name\n * @property nodeType - Type of node (input, transform, conditional, output, etc.)\n */\nexport type FlowEventNodeStart = {\n jobId: string;\n flowId: string;\n nodeId: string;\n eventType: EventType.NodeStart;\n nodeName: string;\n nodeType: NodeType;\n};\n\n/**\n * Event emitted when a node fails after all retry attempts.\n *\n * @property error - Error message from the failed execution\n * @property retryCount - Number of retry attempts made before giving up\n */\nexport type FlowEventNodeError = {\n jobId: string;\n flowId: string;\n nodeId: string;\n nodeName: string;\n eventType: EventType.NodeError;\n error: string;\n retryCount?: number; // Number of retries attempted before failure\n};\n\n/**\n * Event emitted when a node completes successfully.\n *\n * @property result - The output data produced by the node\n */\nexport type FlowEventNodeEnd = {\n jobId: string;\n flowId: string;\n nodeId: string;\n eventType: EventType.NodeEnd;\n nodeName: string;\n result?: unknown; // Node execution result\n};\n\n/**\n * Event emitted when a node pauses execution, waiting for additional data.\n *\n * This typically occurs with input nodes that need more chunks or nodes\n * waiting for external services.\n *\n * @property partialData - Any partial result available before pausing\n */\nexport type FlowEventNodePause = {\n jobId: string;\n flowId: string;\n nodeId: string;\n eventType: EventType.NodePause;\n nodeName: string;\n partialData?: unknown; // Partial result from waiting node\n};\n\n/**\n * Event emitted when a paused node resumes execution.\n *\n * This occurs after providing the additional data needed by a paused node.\n */\nexport type FlowEventNodeResume = {\n jobId: string;\n flowId: string;\n nodeId: string;\n eventType: EventType.NodeResume;\n nodeName: string;\n nodeType: NodeType;\n};\n\n/**\n * Event emitted for node-specific response data.\n *\n * Used for streaming intermediate results or progress updates.\n */\nexport type FlowEventNodeResponse = {\n jobId: string;\n flowId: string;\n nodeId: string;\n eventType: EventType.NodeResponse;\n nodeName: string;\n data: unknown;\n};\n\n/**\n * Union of all possible flow execution events.\n *\n * This discriminated union allows type-safe event handling based on eventType.\n *\n * @example\n * ```typescript\n * function handleFlowEvent(event: FlowEvent) {\n * switch (event.eventType) {\n * case EventType.FlowStart:\n * console.log(\"Flow started:\", event.flowId);\n * break;\n * case EventType.NodeEnd:\n * console.log(\"Node completed:\", event.nodeName, event.result);\n * break;\n * case EventType.FlowError:\n * console.error(\"Flow failed:\", event.error);\n * break;\n * }\n * }\n * ```\n */\nexport type FlowEvent =\n | FlowEventJobStart\n | FlowEventJobEnd\n | FlowEventFlowStart\n | FlowEventFlowEnd\n | FlowEventFlowError\n | FlowEventNodeStart\n | FlowEventNodeEnd\n | FlowEventNodePause\n | FlowEventNodeResume\n | FlowEventNodeError;\n","import { Effect } from \"effect\";\nimport type { z } from \"zod\";\nimport { UploadistaError } from \"../errors\";\nimport type {\n FlowNode,\n FlowNodeData,\n NodeExecutionResult,\n} from \"./types/flow-types\";\n\n/**\n * Defines the type of node in a flow, determining its role in the processing pipeline.\n */\nexport enum NodeType {\n /** Entry point for data into the flow */\n input = \"input\",\n /** Transforms data during flow execution */\n process = \"process\",\n /** Saves data to storage backends */\n output = \"output\",\n /** Routes data based on conditions */\n conditional = \"conditional\",\n /** Splits data to multiple outputs */\n multiplex = \"multiplex\",\n /** Combines multiple inputs into one output */\n merge = \"merge\",\n}\n\n/**\n * Fields that can be evaluated in conditional node conditions.\n * These fields are typically found in file metadata.\n */\nexport type ConditionField =\n | \"mimeType\"\n | \"size\"\n | \"width\"\n | \"height\"\n | \"extension\";\n\n/**\n * Operators available for comparing values in conditional node conditions.\n */\nexport type ConditionOperator =\n | \"equals\"\n | \"notEquals\"\n | \"greaterThan\"\n | \"lessThan\"\n | \"contains\"\n | \"startsWith\";\n\n/**\n * Value used in conditional node comparisons.\n * Can be either a string or number depending on the field being evaluated.\n */\nexport type ConditionValue = string | number;\n\n/**\n * Creates a flow node with automatic input/output validation and retry logic.\n *\n * Flow nodes are the building blocks of processing pipelines. Each node:\n * - Validates its input against a Zod schema\n * - Executes its processing logic\n * - Validates its output against a Zod schema\n * - Can optionally retry on failure with exponential backoff\n *\n * @template Input - The expected input type for this node\n * @template Output - The output type produced by this node\n *\n * @param config - Node configuration\n * @param config.id - Unique identifier for this node in the flow\n * @param config.name - Human-readable name for the node\n * @param config.description - Description of what this node does\n * @param config.type - The type of node (input, process, output, conditional, multiplex, merge)\n * @param config.inputSchema - Zod schema for validating input data\n * @param config.outputSchema - Zod schema for validating output data\n * @param config.run - The processing function to execute for this node\n * @param config.condition - Optional condition for conditional nodes to determine if they should execute\n * @param config.multiInput - If true, node receives all inputs as a record instead of a single input\n * @param config.multiOutput - If true, node can output to multiple targets\n * @param config.pausable - If true, node can pause execution and wait for additional data\n * @param config.retry - Optional retry configuration for handling transient failures\n * @param config.retry.maxRetries - Maximum number of retry attempts (default: 0)\n * @param config.retry.retryDelay - Base delay in milliseconds between retries (default: 1000)\n * @param config.retry.exponentialBackoff - Whether to use exponential backoff for retries (default: true)\n *\n * @returns An Effect that succeeds with the created FlowNode\n *\n * @example\n * ```typescript\n * const resizeNode = createFlowNode({\n * id: \"resize-1\",\n * name: \"Resize Image\",\n * description: \"Resizes images to 800x600\",\n * type: NodeType.process,\n * inputSchema: z.object({\n * stream: z.instanceof(Uint8Array),\n * metadata: z.object({ width: z.number(), height: z.number() })\n * }),\n * outputSchema: z.object({\n * stream: z.instanceof(Uint8Array),\n * metadata: z.object({ width: z.literal(800), height: z.literal(600) })\n * }),\n * run: ({ data }) => Effect.gen(function* () {\n * const resized = yield* resizeImage(data.stream, 800, 600);\n * return {\n * type: \"complete\",\n * data: { stream: resized, metadata: { width: 800, height: 600 } }\n * };\n * }),\n * retry: {\n * maxRetries: 3,\n * retryDelay: 1000,\n * exponentialBackoff: true\n * }\n * });\n * ```\n */\nexport function createFlowNode<Input, Output>({\n id,\n name,\n description,\n type,\n inputSchema,\n outputSchema,\n run,\n condition,\n multiInput = false,\n multiOutput = false,\n pausable = false,\n retry,\n}: {\n id: string;\n name: string;\n description: string;\n type: NodeType;\n inputSchema: z.ZodSchema<Input>;\n outputSchema: z.ZodSchema<Output>;\n run: (args: {\n data: Input;\n jobId: string;\n storageId: string;\n flowId: string;\n clientId: string | null;\n }) => Effect.Effect<NodeExecutionResult<Output>, UploadistaError>;\n condition?: {\n field: ConditionField;\n operator: ConditionOperator;\n value: ConditionValue;\n };\n multiInput?: boolean;\n multiOutput?: boolean;\n pausable?: boolean;\n retry?: {\n maxRetries?: number;\n retryDelay?: number;\n exponentialBackoff?: boolean;\n };\n}): Effect.Effect<FlowNode<Input, Output, UploadistaError>> {\n return Effect.succeed({\n id,\n name,\n description,\n type,\n inputSchema,\n outputSchema,\n pausable,\n run: ({\n data,\n jobId,\n flowId,\n storageId,\n clientId,\n }: {\n data: Input;\n jobId: string;\n flowId: string;\n storageId: string;\n clientId: string | null;\n }) =>\n Effect.gen(function* () {\n // Validate input data against schema\n const validatedData = yield* Effect.try({\n try: () => inputSchema.parse(data),\n catch: (error) => {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n return UploadistaError.fromCode(\"FLOW_INPUT_VALIDATION_ERROR\", {\n body: `Node '${name}' (${id}) input validation failed: ${errorMessage}`,\n cause: error,\n });\n },\n });\n\n // Run the node logic\n const result = yield* run({\n data: validatedData,\n jobId,\n storageId,\n flowId,\n clientId,\n });\n\n // If the node returned waiting state, pass it through\n if (result.type === \"waiting\") {\n return result;\n }\n\n // Validate output data against schema for completed results\n const validatedResult = yield* Effect.try({\n try: () => outputSchema.parse(result.data),\n catch: (error) => {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n return UploadistaError.fromCode(\"FLOW_OUTPUT_VALIDATION_ERROR\", {\n body: `Node '${name}' (${id}) output validation failed: ${errorMessage}`,\n cause: error,\n });\n },\n });\n\n return { type: \"complete\" as const, data: validatedResult };\n }),\n condition,\n multiInput,\n multiOutput,\n retry,\n });\n}\n\n/**\n * Extracts serializable node metadata from a FlowNode instance.\n *\n * This function is useful for serializing flow configurations or\n * transmitting node information over the network without including\n * the executable run function or schemas.\n *\n * @param node - The flow node to extract data from\n * @returns A plain object containing the node's metadata (id, name, description, type)\n */\nexport const getNodeData = (\n // biome-ignore lint/suspicious/noExplicitAny: maybe type later\n node: FlowNode<any, any, UploadistaError>,\n): FlowNodeData => {\n return {\n id: node.id,\n name: node.name,\n description: node.description,\n type: node.type,\n };\n};\n","/**\n * Parallel execution scheduler for flow nodes.\n *\n * The ParallelScheduler analyzes flow dependencies and groups nodes into execution\n * levels where nodes at the same level can run in parallel. It manages concurrency\n * using Effect's built-in concurrency control to prevent resource exhaustion.\n *\n * @module flow/parallel-scheduler\n * @see {@link ParallelScheduler} for the main scheduler class\n *\n * @remarks\n * This scheduler groups nodes by execution level (respecting dependencies) and executes\n * each level in parallel with controlled concurrency. Levels are executed sequentially\n * to ensure dependencies are satisfied before dependent nodes execute.\n *\n * @example\n * ```typescript\n * const scheduler = new ParallelScheduler({ maxConcurrency: 4 });\n *\n * // Group nodes by execution level\n * const levels = scheduler.groupNodesByExecutionLevel(nodes, edges);\n *\n * // Execute nodes in a level with Effect\n * const results = yield* scheduler.executeNodesInParallel([\n * () => executeNode(\"node1\"),\n * () => executeNode(\"node2\"),\n * () => executeNode(\"node3\")\n * ]);\n * ```\n */\n\nimport { Effect } from \"effect\";\nimport type { FlowNode } from \"./types/flow-types\";\n\n/**\n * Represents a level in the execution hierarchy where all nodes can run in parallel.\n *\n * @property level - The execution level (0 = first to execute, higher = later)\n * @property nodes - Array of node IDs that can execute in parallel at this level\n *\n * @example\n * ```\n * Level 0: [input_node] (no dependencies)\n * Level 1: [resize, optimize] (all depend on level 0)\n * Level 2: [storage] (depends on level 1)\n * ```\n */\nexport interface ExecutionLevel {\n level: number;\n nodes: string[];\n}\n\n/**\n * Configuration options for the ParallelScheduler.\n *\n * @property maxConcurrency - Maximum number of nodes to execute in parallel (default: 4)\n * Controls how many nodes run simultaneously within a level\n *\n * @example\n * ```typescript\n * const scheduler = new ParallelScheduler({ maxConcurrency: 8 });\n * ```\n */\nexport interface ParallelSchedulerConfig {\n maxConcurrency?: number;\n}\n\n/**\n * Scheduler for executing flow nodes in parallel while respecting dependencies.\n *\n * The scheduler performs topological sorting to identify nodes that can run\n * concurrently, groups them into execution levels, and provides methods to\n * execute them with controlled concurrency using Effect.\n *\n * Key responsibilities:\n * - Analyze flow dependencies and detect cycles\n * - Group nodes into parallel execution levels\n * - Execute levels in parallel with concurrency limits\n * - Provide utilities to check parallel execution feasibility\n */\nexport class ParallelScheduler {\n private maxConcurrency: number;\n\n /**\n * Creates a new ParallelScheduler instance.\n *\n * @param config - Configuration for the scheduler\n * @example\n * ```typescript\n * const scheduler = new ParallelScheduler({ maxConcurrency: 4 });\n * ```\n */\n constructor(config: ParallelSchedulerConfig = {}) {\n this.maxConcurrency = config.maxConcurrency ?? 4;\n }\n\n /**\n * Groups nodes into execution levels where nodes in the same level can run in parallel.\n *\n * Uses Kahn's algorithm to perform topological sorting with level identification.\n * Nodes are grouped by their distance from source nodes (input nodes with no dependencies).\n *\n * @param nodes - Array of flow nodes to analyze\n * @param edges - Array of edges defining dependencies between nodes\n * @returns Array of execution levels, ordered from 0 (no dependencies) onwards\n * @throws Error if a cycle is detected in the flow graph\n *\n * @example\n * ```typescript\n * const levels = scheduler.groupNodesByExecutionLevel(nodes, edges);\n * // levels = [\n * // { level: 0, nodes: ['input_1'] },\n * // { level: 1, nodes: ['resize_1', 'optimize_1'] },\n * // { level: 2, nodes: ['output_1'] }\n * // ]\n * ```\n */\n groupNodesByExecutionLevel(\n nodes: FlowNode<unknown, unknown>[],\n edges: Array<{ source: string; target: string }>,\n ): ExecutionLevel[] {\n // Build dependency graph\n const graph: Record<string, string[]> = {};\n const inDegree: Record<string, number> = {};\n\n // Initialize graph structure\n nodes.forEach((node) => {\n graph[node.id] = [];\n inDegree[node.id] = 0;\n });\n\n // Build edges and calculate in-degrees\n edges.forEach((edge) => {\n graph[edge.source]?.push(edge.target);\n inDegree[edge.target] = (inDegree[edge.target] || 0) + 1;\n });\n\n const levels: ExecutionLevel[] = [];\n const processedNodes = new Set<string>();\n let levelIndex = 0;\n\n // Use Kahn's algorithm to group nodes by level\n while (processedNodes.size < nodes.length) {\n // Find all nodes with zero in-degree that haven't been processed\n const currentLevelNodes = Object.keys(inDegree).filter(\n (nodeId) => inDegree[nodeId] === 0 && !processedNodes.has(nodeId),\n );\n\n if (currentLevelNodes.length === 0) {\n throw new Error(\n \"Cycle detected in flow graph - cannot execute in parallel\",\n );\n }\n\n levels.push({\n level: levelIndex++,\n nodes: currentLevelNodes,\n });\n\n // Remove current level nodes and update in-degrees for dependent nodes\n currentLevelNodes.forEach((nodeId) => {\n processedNodes.add(nodeId);\n delete inDegree[nodeId];\n\n // Decrease in-degree for all nodes that depend on this node\n graph[nodeId]?.forEach((dependentId) => {\n if (inDegree[dependentId] !== undefined) {\n inDegree[dependentId]--;\n }\n });\n });\n }\n\n return levels;\n }\n\n /**\n * Executes a batch of Effect-based node executors in parallel with concurrency control.\n *\n * All executors are run in parallel, but the number of concurrent executions is limited\n * by maxConcurrency. This prevents resource exhaustion while maximizing parallelism.\n *\n * @template T - The return type of each executor\n * @template E - The error type of the Effects\n * @template R - The requirements type of the Effects\n *\n * @param nodeExecutors - Array of Effect-returning functions to execute in parallel\n * @returns Effect that resolves to array of results in the same order as input\n *\n * @example\n * ```typescript\n * const results = yield* scheduler.executeNodesInParallel([\n * () => executeNode(\"node1\"),\n * () => executeNode(\"node2\"),\n * () => executeNode(\"node3\")\n * ]);\n * // results will be in order: [result1, result2, result3]\n * ```\n */\n executeNodesInParallel<T, E, R>(\n nodeExecutors: Array<() => Effect.Effect<T, E, R>>,\n ): Effect.Effect<T[], E, R> {\n return Effect.all(\n nodeExecutors.map((executor) => executor()),\n {\n concurrency: this.maxConcurrency,\n },\n );\n }\n\n /**\n * Determines if a set of nodes can be safely executed in parallel.\n *\n * Nodes can execute in parallel if all their dependencies have been completed.\n * This is typically called to verify that nodes in an execution level are ready\n * to run given the current node results.\n *\n * @param nodeIds - Array of node IDs to check\n * @param nodeResults - Map of completed node IDs to their results\n * @param reverseGraph - Dependency graph mapping node IDs to their incoming dependencies\n * @returns true if all dependencies for all nodes are in nodeResults, false otherwise\n *\n * @example\n * ```typescript\n * const canRun = scheduler.canExecuteInParallel(\n * ['resize_1', 'optimize_1'],\n * nodeResults,\n * reverseGraph\n * );\n * ```\n */\n canExecuteInParallel(\n nodeIds: string[],\n nodeResults: Map<string, unknown>,\n reverseGraph: Record<string, string[]>,\n ): boolean {\n return nodeIds.every((nodeId) => {\n const dependencies = reverseGraph[nodeId] || [];\n return dependencies.every((depId) => nodeResults.has(depId));\n });\n }\n\n /**\n * Gets execution statistics for monitoring and debugging.\n *\n * @returns Object containing current scheduler configuration\n *\n * @example\n * ```typescript\n * const stats = scheduler.getStats();\n * console.log(`Max concurrency: ${stats.maxConcurrency}`);\n * ```\n */\n getStats() {\n return {\n maxConcurrency: this.maxConcurrency,\n };\n }\n}\n","/** biome-ignore-all lint/suspicious/noExplicitAny: any is used to allow for dynamic types */\nimport type { z } from \"zod\";\n\nimport type {\n FlowEdge,\n FlowNode,\n NodeConnectionValidator,\n TypeCompatibilityChecker,\n} from \"./flow-types\";\n\n// Default type compatibility checker using Zod schemas\nexport const defaultTypeChecker: TypeCompatibilityChecker = (\n fromSchema,\n toSchema,\n) => {\n // Basic schema compatibility rules\n if (fromSchema === toSchema) return true;\n\n // Check if schemas are compatible by comparing their types\n try {\n // For now, assume schemas are compatible if they're both Zod schemas\n // In a more sophisticated system, you'd check actual schema compatibility\n if (\n fromSchema &&\n toSchema &&\n typeof fromSchema === \"object\" &&\n typeof toSchema === \"object\"\n ) {\n return true;\n }\n\n return false;\n } catch {\n // If schema comparison fails, assume compatible\n return true;\n }\n};\n\n// Enhanced type validator with Zod schema support\nexport class FlowTypeValidator implements NodeConnectionValidator {\n private typeChecker: TypeCompatibilityChecker;\n\n constructor(typeChecker: TypeCompatibilityChecker = defaultTypeChecker) {\n this.typeChecker = typeChecker;\n }\n\n validateConnection(\n sourceNode: FlowNode<any, any>,\n targetNode: FlowNode<any, any>,\n _edge: FlowEdge,\n ): boolean {\n // Check if source node output schema is compatible with target node input schema\n return this.getCompatibleTypes(\n sourceNode.outputSchema,\n targetNode.inputSchema,\n );\n }\n\n getCompatibleTypes(\n sourceSchema: z.ZodSchema<any>,\n targetSchema: z.ZodSchema<any>,\n ): boolean {\n return this.typeChecker(sourceSchema, targetSchema);\n }\n\n // Validate entire flow for type compatibility\n validateFlow(\n nodes: FlowNode<any, any>[],\n edges: FlowEdge[],\n ): {\n isValid: boolean;\n errors: string[];\n } {\n const errors: string[] = [];\n const nodeMap = new Map(nodes.map((node) => [node.id, node]));\n\n for (const edge of edges) {\n const sourceNode = nodeMap.get(edge.source);\n const targetNode = nodeMap.get(edge.target);\n\n if (!sourceNode) {\n errors.push(`Source node ${edge.source} not found`);\n continue;\n }\n\n if (!targetNode) {\n errors.push(`Target node ${edge.target} not found`);\n continue;\n }\n\n if (!this.validateConnection(sourceNode, targetNode, edge)) {\n errors.push(\n `Schema mismatch: ${sourceNode.id} output schema incompatible with ${targetNode.id} input schema`,\n );\n }\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n };\n }\n\n // Get expected input schemas for a node based on its incoming edges\n getExpectedInputSchemas(\n nodeId: string,\n nodes: FlowNode<any, any>[],\n edges: FlowEdge[],\n ): Record<string, unknown> {\n const nodeMap = new Map(nodes.map((node) => [node.id, node]));\n const expectedSchemas: Record<string, unknown> = {};\n\n for (const edge of edges) {\n if (edge.target === nodeId) {\n const sourceNode = nodeMap.get(edge.source);\n if (sourceNode) {\n const portKey = edge.sourcePort || edge.source;\n expectedSchemas[portKey] = sourceNode.outputSchema;\n }\n }\n }\n\n return expectedSchemas;\n }\n\n // Get actual output schemas for a node based on its outgoing edges\n getActualOutputSchemas(\n nodeId: string,\n nodes: FlowNode<any, any>[],\n edges: FlowEdge[],\n ): Record<string, unknown> {\n const nodeMap = new Map(nodes.map((node) => [node.id, node]));\n const actualSchemas: Record<string, unknown> = {};\n\n for (const edge of edges) {\n if (edge.source === nodeId) {\n const targetNode = nodeMap.get(edge.target);\n if (targetNode) {\n const portKey = edge.targetPort || edge.target;\n actualSchemas[portKey] = targetNode.inputSchema;\n }\n }\n }\n\n return actualSchemas;\n }\n\n // Validate data against a schema\n validateData(\n data: unknown,\n schema: unknown,\n ): { isValid: boolean; errors: string[] } {\n try {\n (schema as z.ZodSchema<any>).parse(data);\n return { isValid: true, errors: [] };\n } catch (error) {\n if (error instanceof Error && \"errors\" in error) {\n return {\n isValid: false,\n errors: (\n error as { errors: Array<{ path: string[]; message: string }> }\n ).errors.map((err) => `${err.path.join(\".\")}: ${err.message}`),\n };\n }\n return {\n isValid: false,\n errors: [error instanceof Error ? error.message : \"Validation failed\"],\n };\n }\n }\n}\n\n// Utility functions for common type checks\nexport const typeUtils = {\n // Check if a schema is assignable to another\n isAssignable(\n fromSchema: z.ZodSchema<any>,\n toSchema: z.ZodSchema<any>,\n ): boolean {\n return defaultTypeChecker(fromSchema, toSchema);\n },\n\n // Get the most specific common schema\n getCommonSchema(\n schema1: z.ZodSchema<any>,\n schema2: z.ZodSchema<any>,\n ): z.ZodSchema<any> {\n if (schema1 === schema2) return schema1;\n\n // For now, return the more specific schema or schema1\n // In a more sophisticated system, you'd compute the intersection\n return schema1;\n },\n\n // Check if a value matches a schema\n matchesSchema(value: unknown, schema: z.ZodSchema<any>): boolean {\n try {\n schema.parse(value);\n return true;\n } catch {\n return false;\n }\n },\n};\n","/**\n * Core Flow Engine implementation using Effect-based DAG execution.\n *\n * This module implements the Flow Engine, which executes directed acyclic graphs (DAGs)\n * of processing nodes. It supports sequential execution with topological sorting,\n * conditional node execution, retry logic, and pausable flows.\n *\n * @module flow\n * @see {@link createFlowWithSchema} for creating new flows\n * @see {@link Flow} for the flow type definition\n */\n\n/** biome-ignore-all lint/suspicious/noExplicitAny: any is used to allow for dynamic types */\n\nimport { Effect } from \"effect\";\nimport { z } from \"zod\";\n\nimport { UploadistaError } from \"../errors\";\nimport type { FlowEdge } from \"./edge\";\nimport { EventType } from \"./event\";\nimport { getNodeData } from \"./node\";\nimport { ParallelScheduler } from \"./parallel-scheduler\";\nimport type { FlowConfig, FlowNode, FlowNodeData } from \"./types/flow-types\";\nimport { FlowTypeValidator } from \"./types/type-validator\";\n\n/**\n * Serialized flow data for storage and transport.\n * Contains the minimal information needed to reconstruct a flow.\n *\n * @property id - Unique flow identifier\n * @property name - Human-readable flow name\n * @property nodes - Array of node data (without execution logic)\n * @property edges - Connections between nodes defining data flow\n */\nexport type FlowData = {\n id: string;\n name: string;\n nodes: FlowNodeData[];\n edges: FlowEdge[];\n};\n\n/**\n * Extracts serializable flow data from a Flow instance.\n * Useful for storing flow definitions or sending them over the network.\n *\n * @template TRequirements - Effect requirements for the flow\n * @param flow - Flow instance to extract data from\n * @returns Serializable flow data without execution logic\n *\n * @example\n * ```typescript\n * const flowData = getFlowData(myFlow);\n * // Store in database or send to client\n * await db.flows.save(flowData);\n * ```\n */\nexport const getFlowData = <TRequirements>(\n flow: Flow<any, any, TRequirements>,\n): FlowData => {\n return {\n id: flow.id,\n name: flow.name,\n nodes: flow.nodes.map(getNodeData),\n edges: flow.edges,\n };\n};\n\n/**\n * Result of a flow execution - either completed or paused.\n *\n * @template TOutput - Type of the flow's output data\n *\n * @remarks\n * Flows can pause when a node needs additional data (e.g., waiting for user input\n * or external service). The execution state allows resuming from where it paused.\n *\n * @example\n * ```typescript\n * const result = await Effect.runPromise(flow.run({ inputs, storageId, jobId }));\n *\n * if (result.type === \"completed\") {\n * console.log(\"Flow completed:\", result.result);\n * } else {\n * console.log(\"Flow paused at node:\", result.nodeId);\n * // Can resume later with: flow.resume({ jobId, executionState: result.executionState, ... })\n * }\n * ```\n */\nexport type FlowExecutionResult<TOutput> =\n | { type: \"completed\"; result: TOutput }\n | {\n type: \"paused\";\n nodeId: string;\n executionState: {\n executionOrder: string[];\n currentIndex: number;\n inputs: Record<string, unknown>;\n };\n };\n\n/**\n * A Flow represents a directed acyclic graph (DAG) of processing nodes.\n *\n * Flows execute nodes in topological order, passing data between nodes through edges.\n * They support conditional execution, retry logic, pausable nodes, and event emission.\n *\n * @template TFlowInputSchema - Zod schema defining the shape of input data\n * @template TFlowOutputSchema - Zod schema defining the shape of output data\n * @template TRequirements - Effect requirements (services/contexts) needed by nodes\n *\n * @property id - Unique flow identifier\n * @property name - Human-readable flow name\n * @property nodes - Array of nodes in the flow\n * @property edges - Connections between nodes\n * @property inputSchema - Zod schema for validating flow inputs\n * @property outputSchema - Zod schema for validating flow outputs\n * @property onEvent - Optional callback for flow execution events\n * @property run - Executes the flow from the beginning\n * @property resume - Resumes a paused flow execution\n * @property validateTypes - Validates node type compatibility\n * @property validateInputs - Validates input data against schema\n * @property validateOutputs - Validates output data against schema\n *\n * @remarks\n * Flows are created using {@link createFlowWithSchema}. The Effect-based design\n * allows for composable error handling, resource management, and dependency injection.\n *\n * @example\n * ```typescript\n * const flow = yield* createFlowWithSchema({\n * flowId: \"image-pipeline\",\n * name: \"Image Processing Pipeline\",\n * nodes: [inputNode, resizeNode, optimizeNode, storageNode],\n * edges: [\n * { source: \"input\", target: \"resize\" },\n * { source: \"resize\", target: \"optimize\" },\n * { source: \"optimize\", target: \"storage\" }\n * ],\n * inputSchema: z.object({ file: z.instanceof(File) }),\n * outputSchema: uploadFileSchema\n * });\n *\n * const result = yield* flow.run({\n * inputs: { input: { file: myFile } },\n * storageId: \"storage-1\",\n * jobId: \"job-123\"\n * });\n * ```\n */\nexport type Flow<\n TFlowInputSchema extends z.ZodSchema<any>,\n TFlowOutputSchema extends z.ZodSchema<any>,\n TRequirements,\n> = {\n id: string;\n name: string;\n nodes: FlowNode<any, any, UploadistaError>[];\n edges: FlowEdge[];\n inputSchema: TFlowInputSchema;\n outputSchema: TFlowOutputSchema;\n onEvent?: FlowConfig<\n TFlowInputSchema,\n TFlowOutputSchema,\n TRequirements\n >[\"onEvent\"];\n run: (args: {\n inputs?: Record<string, z.infer<TFlowInputSchema>>;\n storageId: string;\n jobId: string;\n clientId: string | null;\n }) => Effect.Effect<\n FlowExecutionResult<Record<string, z.infer<TFlowOutputSchema>>>,\n UploadistaError,\n TRequirements\n >;\n resume: (args: {\n jobId: string;\n storageId: string;\n nodeResults: Record<string, unknown>; // Reconstructed from tasks\n executionState: {\n executionOrder: string[];\n currentIndex: number;\n inputs: Record<string, z.infer<TFlowInputSchema>>;\n };\n clientId: string | null;\n }) => Effect.Effect<\n FlowExecutionResult<Record<string, z.infer<TFlowOutputSchema>>>,\n UploadistaError,\n TRequirements\n >;\n validateTypes: () => { isValid: boolean; errors: string[] };\n validateInputs: (inputs: unknown) => { isValid: boolean; errors: string[] };\n validateOutputs: (outputs: unknown) => { isValid: boolean; errors: string[] };\n};\n\n/**\n * Creates a new Flow with Zod schema-based type validation.\n *\n * This is the primary way to create flows in Uploadista. It constructs a Flow\n * instance that validates inputs/outputs, executes nodes in topological order,\n * handles errors with retries, and emits events during execution.\n *\n * @template TFlowInputSchema - Zod schema for flow input validation\n * @template TFlowOutputSchema - Zod schema for flow output validation\n * @template TRequirements - Effect requirements/services needed by the flow\n * @template TNodeError - Union of possible errors from nodes\n * @template TNodeRequirements - Union of requirements from nodes\n *\n * @param config - Flow configuration object\n * @param config.flowId - Unique identifier for the flow\n * @param config.name - Human-readable flow name\n * @param config.nodes - Array of nodes (can be plain nodes or Effects resolving to nodes)\n * @param config.edges - Array of edges connecting nodes\n * @param config.inputSchema - Zod schema for validating inputs\n * @param config.outputSchema - Zod schema for validating outputs\n * @param config.typeChecker - Optional custom type compatibility checker\n * @param config.onEvent - Optional event callback for monitoring execution\n *\n * @returns Effect that resolves to a Flow instance\n *\n * @throws {UploadistaError} FLOW_CYCLE_ERROR if the graph contains cycles\n * @throws {UploadistaError} FLOW_NODE_NOT_FOUND if a node is referenced but missing\n * @throws {UploadistaError} FLOW_NODE_ERROR if node execution fails\n * @throws {UploadistaError} FLOW_OUTPUT_VALIDATION_ERROR if outputs don't match schema\n *\n * @remarks\n * - Nodes can be provided as plain objects or as Effects that resolve to nodes\n * - The flow performs topological sorting to determine execution order\n * - Conditional nodes are evaluated before execution\n * - Nodes can specify retry configuration with exponential backoff\n * - Pausable nodes can halt execution and resume later\n *\n * @example\n * ```typescript\n * const flow = yield* createFlowWithSchema({\n * flowId: \"image-upload\",\n * name: \"Image Upload with Processing\",\n * nodes: [\n * inputNode,\n * yield* createResizeNode({ width: 1920, height: 1080 }),\n * optimizeNode,\n * storageNode\n * ],\n * edges: [\n * { source: \"input\", target: \"resize\" },\n * { source: \"resize\", target: \"optimize\" },\n * { source: \"optimize\", target: \"storage\" }\n * ],\n * inputSchema: z.object({\n * file: z.instanceof(File),\n * metadata: z.record(z.string(), z.any()).optional()\n * }),\n * outputSchema: uploadFileSchema,\n * onEvent: (event) => Effect.gen(function* () {\n * console.log(\"Flow event:\", event);\n * return { eventId: event.jobId };\n * })\n * });\n * ```\n *\n * @see {@link Flow} for the returned flow type\n * @see {@link FlowConfig} for configuration options\n */\nexport function createFlowWithSchema<\n TFlowInputSchema extends z.ZodSchema<any>,\n TFlowOutputSchema extends z.ZodSchema<any>,\n TRequirements = never,\n TNodeError = never,\n TNodeRequirements = never,\n>(\n config: FlowConfig<\n TFlowInputSchema,\n TFlowOutputSchema,\n TNodeError,\n TNodeRequirements\n >,\n): Effect.Effect<\n Flow<TFlowInputSchema, TFlowOutputSchema, TRequirements>,\n TNodeError,\n TNodeRequirements\n> {\n return Effect.gen(function* () {\n // Resolve nodes - handle mixed arrays of pure nodes and Effect nodes\n const resolvedNodes: Array<FlowNode<any, any, UploadistaError>> =\n yield* Effect.all(\n config.nodes.map((node) =>\n Effect.isEffect(node)\n ? (node as Effect.Effect<\n FlowNode<any, any, UploadistaError>,\n TNodeError,\n TNodeRequirements\n >)\n : Effect.succeed(node as FlowNode<any, any, UploadistaError>),\n ),\n );\n\n const {\n flowId,\n name,\n onEvent,\n edges,\n inputSchema,\n outputSchema,\n typeChecker,\n } = config;\n const nodes = resolvedNodes;\n const typeValidator = new FlowTypeValidator(typeChecker);\n\n // Build adjacency list for topological sorting\n const buildGraph = () => {\n const graph: Record<string, string[]> = {};\n const inDegree: Record<string, number> = {};\n const reverseGraph: Record<string, string[]> = {};\n\n // Initialize\n nodes.forEach((node: any) => {\n graph[node.id] = [];\n reverseGraph[node.id] = [];\n inDegree[node.id] = 0;\n });\n\n // Build edges\n edges.forEach((edge: any) => {\n graph[edge.source]?.push(edge.target);\n reverseGraph[edge.target]?.push(edge.source);\n inDegree[edge.target] = (inDegree[edge.target] || 0) + 1;\n });\n\n return { graph, reverseGraph, inDegree };\n };\n\n // Topological sort to determine execution order\n const topologicalSort = () => {\n const { graph, inDegree } = buildGraph();\n const queue: string[] = [];\n const result: string[] = [];\n\n // Add nodes with no incoming edges\n Object.keys(inDegree).forEach((nodeId) => {\n if (inDegree[nodeId] === 0) {\n queue.push(nodeId);\n }\n });\n\n while (queue.length > 0) {\n const current = queue.shift();\n if (!current) {\n throw new Error(\"No current node found\");\n }\n result.push(current);\n\n graph[current]?.forEach((neighbor: any) => {\n inDegree[neighbor] = (inDegree[neighbor] || 0) - 1;\n if (inDegree[neighbor] === 0) {\n queue.push(neighbor);\n }\n });\n }\n\n return result;\n };\n\n // Evaluate condition for conditional nodes using Effect\n const evaluateCondition = (\n node: FlowNode<any, any, UploadistaError>,\n data: unknown,\n ): Effect.Effect<boolean, never> => {\n if (!node.condition) return Effect.succeed(true);\n\n const { field, operator, value } = node.condition;\n const dataRecord = data as Record<string, unknown>;\n const metadata = dataRecord?.metadata as\n | Record<string, unknown>\n | undefined;\n const fieldValue = metadata?.[field] || dataRecord?.[field];\n\n const result = (() => {\n switch (operator) {\n case \"equals\":\n return fieldValue === value;\n case \"notEquals\":\n return fieldValue !== value;\n case \"greaterThan\":\n return Number(fieldValue) > Number(value);\n case \"lessThan\":\n return Number(fieldValue) < Number(value);\n case \"contains\":\n return String(fieldValue).includes(String(value));\n case \"startsWith\":\n return String(fieldValue).startsWith(String(value));\n default:\n return true;\n }\n })();\n\n return Effect.succeed(result);\n };\n\n // Get all inputs for a node\n const getNodeInputs = (\n nodeId: string,\n nodeResults: Map<string, unknown>,\n ) => {\n const { reverseGraph } = buildGraph();\n const incomingNodes = reverseGraph[nodeId] || [];\n const inputs: Record<string, unknown> = {};\n\n incomingNodes.forEach((sourceNodeId: any) => {\n const result = nodeResults.get(sourceNodeId);\n if (result !== undefined) {\n inputs[sourceNodeId] = result;\n }\n });\n\n return inputs;\n };\n\n // Map flow inputs to input nodes\n const mapFlowInputsToNodes = (\n flowInputs: Record<string, z.infer<TFlowInputSchema>>,\n ) => {\n const inputNodes = nodes.filter((node: any) => node.type === \"input\");\n const mappedInputs: Record<string, z.infer<TFlowInputSchema>> = {};\n\n inputNodes.forEach((node: any) => {\n if (\n flowInputs &&\n typeof flowInputs === \"object\" &&\n node.id in flowInputs\n ) {\n mappedInputs[node.id] = inputSchema.parse(flowInputs[node.id]);\n }\n });\n\n return mappedInputs;\n };\n\n // Collect outputs from output nodes\n const collectFlowOutputs = (\n nodeResults: Map<string, unknown>,\n ): Record<string, z.infer<TFlowInputSchema>> => {\n const outputNodes = nodes.filter((node: any) => node.type === \"output\");\n const flowOutputs: Record<string, unknown> = {};\n\n outputNodes.forEach((node: any) => {\n const result = nodeResults.get(node.id);\n if (result !== undefined) {\n flowOutputs[node.id] = result;\n }\n });\n\n return flowOutputs as Record<string, z.infer<TFlowInputSchema>>;\n };\n\n // Execute a single node using Effect\n const executeNode = (\n nodeId: string,\n storageId: string,\n nodeInputs: Record<string, z.infer<TFlowInputSchema>>,\n nodeResults: Map<string, unknown>,\n nodeMap: Map<string, FlowNode<any, any, UploadistaError>>,\n jobId: string,\n clientId: string | null,\n ): Effect.Effect<\n { nodeId: string; result: unknown; success: boolean; waiting: boolean },\n UploadistaError\n > => {\n return Effect.gen(function* () {\n const node = nodeMap.get(nodeId);\n if (!node) {\n return yield* UploadistaError.fromCode(\n \"FLOW_NODE_NOT_FOUND\",\n ).toEffect();\n }\n\n // Emit NodeStart event if provided\n if (onEvent) {\n yield* onEvent({\n jobId,\n flowId,\n nodeId,\n eventType: EventType.NodeStart,\n nodeName: node.name,\n nodeType: node.type,\n });\n }\n\n // Get retry configuration\n const maxRetries = node.retry?.maxRetries ?? 0;\n const baseDelay = node.retry?.retryDelay ?? 1000;\n const useExponentialBackoff = node.retry?.exponentialBackoff ?? true;\n\n let retryCount = 0;\n let lastError: UploadistaError | null = null;\n\n // Retry loop\n while (retryCount <= maxRetries) {\n try {\n // Prepare input data for the node\n let nodeInput: unknown;\n let nodeInputsForExecution: Record<string, unknown> = {};\n\n if (node.type === \"input\") {\n // For input nodes, use the mapped flow input\n nodeInput = nodeInputs[nodeId];\n if (nodeInput === undefined) {\n return yield* UploadistaError.fromCode(\"FLOW_NODE_ERROR\", {\n cause: new Error(`Input node ${nodeId} has no input data`),\n }).toEffect();\n }\n } else {\n // Get all inputs for the node\n nodeInputsForExecution = getNodeInputs(nodeId, nodeResults);\n\n if (Object.keys(nodeInputsForExecution).length === 0) {\n return yield* UploadistaError.fromCode(\"FLOW_NODE_ERROR\", {\n cause: new Error(`Node ${nodeId} has no input data`),\n }).toEffect();\n }\n\n // For single input nodes, use the first input\n if (!node.multiInput) {\n const firstInputKey = Object.keys(nodeInputsForExecution)[0];\n if (!firstInputKey) {\n return yield* UploadistaError.fromCode(\"FLOW_NODE_ERROR\", {\n cause: new Error(`Node ${nodeId} has no input data`),\n }).toEffect();\n }\n nodeInput = nodeInputsForExecution[firstInputKey];\n } else {\n // For multi-input nodes, pass all inputs\n nodeInput = nodeInputsForExecution;\n }\n }\n\n // Check condition for conditional nodes\n if (node.type === \"conditional\") {\n const conditionResult = yield* evaluateCondition(node, nodeInput);\n if (!conditionResult) {\n // Skip this node - return success but no result\n if (onEvent) {\n yield* onEvent({\n jobId,\n flowId,\n nodeId,\n eventType: EventType.NodeEnd,\n nodeName: node.name,\n });\n }\n return {\n nodeId,\n result: nodeInput,\n success: true,\n waiting: false,\n };\n }\n }\n\n // Execute the node\n const executionResult = yield* node.run({\n data: nodeInput,\n inputs: nodeInputsForExecution,\n jobId,\n flowId,\n storageId,\n clientId,\n });\n\n // Handle execution result\n if (executionResult.type === \"waiting\") {\n // Node is waiting for more data - pause execution\n const result = executionResult.partialData;\n\n // Emit NodePause event with partial data result\n if (onEvent) {\n yield* onEvent({\n jobId,\n flowId,\n nodeId,\n eventType: EventType.NodePause,\n nodeName: node.name,\n partialData: result,\n });\n }\n\n return {\n nodeId,\n result,\n success: true,\n waiting: true,\n };\n }\n\n // Node completed successfully\n const result = executionResult.data;\n\n // Emit NodeEnd event with result\n if (onEvent) {\n yield* onEvent({\n jobId,\n flowId,\n nodeId,\n eventType: EventType.NodeEnd,\n nodeName: node.name,\n result,\n });\n }\n\n return { nodeId, result, success: true, waiting: false };\n } catch (error) {\n // Store the error\n lastError =\n error instanceof UploadistaError\n ? error\n : UploadistaError.fromCode(\"FLOW_NODE_ERROR\", { cause: error });\n\n // Check if we should retry\n if (retryCount < maxRetries) {\n retryCount++;\n\n // Calculate delay with exponential backoff if enabled\n const delay = useExponentialBackoff\n ? baseDelay * 2 ** (retryCount - 1)\n : baseDelay;\n\n // Log retry attempt\n yield* Effect.logWarning(\n `Node ${nodeId} (${node.name}) failed, retrying (${retryCount}/${maxRetries}) after ${delay}ms`,\n );\n\n // Wait before retrying\n yield* Effect.sleep(delay);\n\n // Continue to next iteration of retry loop\n continue;\n }\n\n // No more retries - emit final error event\n if (onEvent) {\n yield* onEvent({\n jobId,\n flowId,\n nodeId,\n eventType: EventType.NodeError,\n nodeName: node.name,\n error: lastError.body,\n retryCount,\n });\n }\n\n return yield* lastError.toEffect();\n }\n }\n\n // If we get here, all retries failed\n if (lastError) {\n return yield* lastError.toEffect();\n }\n\n // Should never reach here\n return yield* UploadistaError.fromCode(\"FLOW_NODE_ERROR\", {\n cause: new Error(\"Unexpected error in retry loop\"),\n }).toEffect();\n });\n };\n\n // Internal execution function that can start fresh or resume\n const executeFlow = ({\n inputs,\n storageId,\n jobId,\n resumeFrom,\n clientId,\n }: {\n inputs?: Record<string, z.infer<TFlowInputSchema>>;\n storageId: string;\n jobId: string;\n resumeFrom?: {\n executionOrder: string[];\n nodeResults: Map<string, unknown>;\n currentIndex: number;\n };\n clientId: string | null;\n }): Effect.Effect<\n | {\n type: \"completed\";\n result: Record<string, z.infer<TFlowOutputSchema>>;\n }\n | {\n type: \"paused\";\n nodeId: string;\n executionState: {\n executionOrder: string[];\n currentIndex: number;\n inputs: Record<string, z.infer<TFlowInputSchema>>;\n };\n },\n UploadistaError\n > => {\n return Effect.gen(function* () {\n // Emit FlowStart event only if starting fresh\n if (!resumeFrom && onEvent) {\n yield* onEvent({\n jobId,\n eventType: EventType.FlowStart,\n flowId,\n });\n }\n\n // Map flow inputs to input nodes\n const nodeInputs = mapFlowInputsToNodes(inputs || {});\n\n // Get execution order and results - either fresh or from resume state\n let executionOrder: string[];\n let nodeResults: Map<string, unknown>;\n let startIndex: number;\n\n if (resumeFrom) {\n // Resume from saved state\n executionOrder = resumeFrom.executionOrder;\n nodeResults = resumeFrom.nodeResults;\n startIndex = resumeFrom.currentIndex;\n } else {\n // Start fresh\n executionOrder = topologicalSort();\n nodeResults = new Map<string, unknown>();\n startIndex = 0;\n\n // Check for cycles\n if (executionOrder.length !== nodes.length) {\n return yield* UploadistaError.fromCode(\n \"FLOW_CYCLE_ERROR\",\n ).toEffect();\n }\n }\n\n // Create node map for quick lookup\n const nodeMap = new Map(nodes.map((node) => [node.id, node]));\n\n // Determine execution strategy\n const useParallelExecution =\n config.parallelExecution?.enabled ?? false;\n\n if (useParallelExecution) {\n // Parallel execution using execution levels\n yield* Effect.logDebug(\n `Flow ${flowId}: Executing in parallel mode (maxConcurrency: ${config.parallelExecution?.maxConcurrency ?? 4})`,\n );\n\n const scheduler = new ParallelScheduler({\n maxConcurrency: config.parallelExecution?.maxConcurrency ?? 4,\n });\n\n // Get execution levels\n const executionLevels = scheduler.groupNodesByExecutionLevel(\n nodes,\n edges,\n );\n\n yield* Effect.logDebug(\n `Flow ${flowId}: Grouped nodes into ${executionLevels.length} execution levels`,\n );\n\n // Build reverse graph for dependency checking\n const reverseGraph: Record<string, string[]> = {};\n nodes.forEach((node) => {\n reverseGraph[node.id] = [];\n });\n edges.forEach((edge) => {\n reverseGraph[edge.target]?.push(edge.source);\n });\n\n // Execute each level sequentially, but nodes within level in parallel\n for (const level of executionLevels) {\n yield* Effect.logDebug(\n `Flow ${flowId}: Executing level ${level.level} with nodes: ${level.nodes.join(\", \")}`,\n );\n\n // Create executor functions for all nodes in this level\n const nodeExecutors = level.nodes.map((nodeId) => () =>\n Effect.gen(function* () {\n // Emit NodeResume event if we're resuming from a paused state at this node\n if (resumeFrom && nodeId === resumeFrom.executionOrder[startIndex] && onEvent) {\n const node = nodeMap.get(nodeId);\n if (node) {\n yield* onEvent({\n jobId,\n flowId,\n nodeId,\n eventType: EventType.NodeResume,\n nodeName: node.name,\n nodeType: node.type,\n });\n }\n }\n\n const nodeResult = yield* executeNode(\n nodeId,\n storageId,\n nodeInputs,\n nodeResults,\n nodeMap,\n jobId,\n clientId,\n );\n\n return { nodeId, nodeResult };\n }),\n );\n\n // Execute all nodes in this level in parallel\n const levelResults = yield* scheduler.executeNodesInParallel(\n nodeExecutors,\n );\n\n // Process results and check for waiting nodes\n for (const { nodeId, nodeResult } of levelResults) {\n if (nodeResult.waiting) {\n // Node is waiting - pause execution and return state\n if (nodeResult.result !== undefined) {\n nodeResults.set(nodeId, nodeResult.result);\n }\n\n return {\n type: \"paused\" as const,\n nodeId,\n executionState: {\n executionOrder,\n currentIndex: executionOrder.indexOf(nodeId),\n inputs: nodeInputs,\n },\n };\n }\n\n if (nodeResult.success) {\n nodeResults.set(nodeId, nodeResult.result);\n }\n }\n }\n } else {\n // Sequential execution (original behavior)\n yield* Effect.logDebug(`Flow ${flowId}: Executing in sequential mode`);\n\n for (let i = startIndex; i < executionOrder.length; i++) {\n const nodeId = executionOrder[i];\n if (!nodeId) {\n return yield* UploadistaError.fromCode(\n \"FLOW_NODE_NOT_FOUND\",\n ).toEffect();\n }\n\n // Emit NodeResume event if we're resuming from a paused state at this node\n if (resumeFrom && i === startIndex && onEvent) {\n const node = nodeMap.get(nodeId);\n if (node) {\n yield* onEvent({\n jobId,\n flowId,\n nodeId,\n eventType: EventType.NodeResume,\n nodeName: node.name,\n nodeType: node.type,\n });\n }\n }\n\n const nodeResult = yield* executeNode(\n nodeId,\n storageId,\n nodeInputs,\n nodeResults,\n nodeMap,\n jobId,\n clientId,\n );\n\n if (nodeResult.waiting) {\n // Node is waiting - pause execution and return state\n if (nodeResult.result !== undefined) {\n nodeResults.set(nodeResult.nodeId, nodeResult.result);\n }\n\n return {\n type: \"paused\" as const,\n nodeId: nodeResult.nodeId,\n executionState: {\n executionOrder,\n currentIndex: i, // Stay at current index to re-execute this node on resume\n inputs: nodeInputs,\n },\n };\n }\n\n if (nodeResult.success) {\n nodeResults.set(nodeResult.nodeId, nodeResult.result);\n }\n }\n }\n\n // All nodes completed - collect outputs\n const finalResult = collectFlowOutputs(nodeResults);\n\n const finalResultSchema = z.record(z.string(), outputSchema);\n\n // Validate the final result against the output schema\n const parseResult = finalResultSchema.safeParse(finalResult);\n if (!parseResult.success) {\n const validationError = `Flow output validation failed: ${parseResult.error.message}. Expected outputs: ${JSON.stringify(Object.keys(collectFlowOutputs(nodeResults)))}. Output nodes: ${nodes\n .filter((n: any) => n.type === \"output\")\n .map((n: any) => n.id)\n .join(\", \")}`;\n\n // Emit FlowError event for validation failure\n if (onEvent) {\n yield* onEvent({\n jobId,\n eventType: EventType.FlowError,\n flowId,\n error: validationError,\n });\n }\n return yield* UploadistaError.fromCode(\n \"FLOW_OUTPUT_VALIDATION_ERROR\",\n {\n body: validationError,\n cause: parseResult.error,\n },\n ).toEffect();\n }\n const validatedResult = parseResult.data;\n\n // Emit FlowEnd event\n if (onEvent) {\n yield* onEvent({\n jobId,\n eventType: EventType.FlowEnd,\n flowId,\n result: validatedResult,\n });\n }\n\n return { type: \"completed\" as const, result: validatedResult };\n });\n };\n\n const run = ({\n inputs,\n storageId,\n jobId,\n clientId,\n }: {\n inputs?: Record<string, z.infer<TFlowInputSchema>>;\n storageId: string;\n jobId: string;\n clientId: string | null;\n }): Effect.Effect<\n | {\n type: \"completed\";\n result: Record<string, z.infer<TFlowOutputSchema>>;\n }\n | {\n type: \"paused\";\n nodeId: string;\n executionState: {\n executionOrder: string[];\n currentIndex: number;\n inputs: Record<string, z.infer<TFlowInputSchema>>;\n };\n },\n UploadistaError,\n TRequirements\n > => {\n return executeFlow({ inputs, storageId, jobId, clientId });\n };\n\n const resume = ({\n jobId,\n storageId,\n nodeResults,\n executionState,\n clientId,\n }: {\n jobId: string;\n storageId: string;\n nodeResults: Record<string, unknown>;\n executionState: {\n executionOrder: string[];\n currentIndex: number;\n inputs: Record<string, z.infer<TFlowInputSchema>>;\n };\n clientId: string | null;\n }): Effect.Effect<\n | {\n type: \"completed\";\n result: Record<string, z.infer<TFlowOutputSchema>>;\n }\n | {\n type: \"paused\";\n nodeId: string;\n executionState: {\n executionOrder: string[];\n currentIndex: number;\n inputs: Record<string, z.infer<TFlowInputSchema>>;\n };\n },\n UploadistaError\n > => {\n return executeFlow({\n inputs: executionState.inputs,\n storageId,\n jobId,\n resumeFrom: {\n executionOrder: executionState.executionOrder,\n nodeResults: new Map(Object.entries(nodeResults)),\n currentIndex: executionState.currentIndex,\n },\n clientId,\n });\n };\n\n const validateTypes = () => {\n // Convert FlowNode to FlowNode for validation\n const compatibleNodes = nodes as FlowNode<any, any>[];\n return typeValidator.validateFlow(compatibleNodes, edges);\n };\n\n const validateInputs = (inputs: unknown) => {\n return typeValidator.validateData(inputs, inputSchema);\n };\n\n const validateOutputs = (outputs: unknown) => {\n return typeValidator.validateData(outputs, outputSchema);\n };\n\n return {\n id: flowId,\n name,\n nodes,\n edges,\n inputSchema,\n outputSchema,\n onEvent,\n run,\n resume,\n validateTypes,\n validateInputs,\n validateOutputs,\n };\n });\n}\n","import { Context, Effect, Layer } from \"effect\";\nimport type { z } from \"zod\";\nimport { UploadistaError } from \"../errors\";\nimport {\n createFlowWithSchema,\n EventType,\n type Flow,\n type FlowData,\n getFlowData,\n runArgsSchema,\n} from \"../flow\";\nimport type {\n EventEmitter,\n KvStore,\n UploadFile,\n WebSocketConnection,\n} from \"../types\";\nimport { FlowEventEmitter, FlowJobKVStore } from \"../types\";\nimport { UploadServer } from \"../upload\";\nimport type { FlowEvent } from \"./event\";\nimport type { FlowJob } from \"./types/flow-job\";\n\n/**\n * Flow provider interface that applications must implement.\n *\n * This interface defines how the FlowServer retrieves flow definitions.\n * Applications provide their own implementation to load flows from a database,\n * configuration files, or any other source.\n *\n * @template TRequirements - Additional Effect requirements for flow execution\n *\n * @property getFlow - Retrieves a flow definition by ID with authorization check\n *\n * @example\n * ```typescript\n * // Implement a flow provider from database\n * const dbFlowProvider: FlowProviderShape = {\n * getFlow: (flowId, clientId) => Effect.gen(function* () {\n * // Load flow from database\n * const flowData = yield* db.getFlow(flowId);\n *\n * // Check authorization\n * if (flowData.ownerId !== clientId) {\n * return yield* Effect.fail(\n * UploadistaError.fromCode(\"FLOW_NOT_AUTHORIZED\")\n * );\n * }\n *\n * // Create flow instance\n * return createFlow(flowData);\n * })\n * };\n *\n * // Provide to FlowServer\n * const flowProviderLayer = Layer.succeed(FlowProvider, dbFlowProvider);\n * ```\n */\nexport type FlowProviderShape<TRequirements = any> = {\n getFlow: (\n flowId: string,\n clientId: string | null,\n ) => Effect.Effect<Flow<any, any, TRequirements>, UploadistaError>;\n};\n\n/**\n * Effect-TS context tag for the FlowProvider service.\n *\n * Applications must provide an implementation of FlowProviderShape\n * to enable the FlowServer to retrieve flow definitions.\n *\n * @example\n * ```typescript\n * // Access FlowProvider in an Effect\n * const effect = Effect.gen(function* () {\n * const provider = yield* FlowProvider;\n * const flow = yield* provider.getFlow(\"flow123\", \"client456\");\n * return flow;\n * });\n * ```\n */\nexport class FlowProvider extends Context.Tag(\"FlowProvider\")<\n FlowProvider,\n FlowProviderShape<any>\n>() {}\n\n/**\n * FlowServer service interface.\n *\n * This is the core flow processing service that executes DAG-based file processing pipelines.\n * It manages flow execution, job tracking, node processing, pause/resume functionality,\n * and real-time event broadcasting.\n *\n * All operations return Effect types for composable, type-safe error handling.\n *\n * @property getFlow - Retrieves a flow definition by ID\n * @property getFlowData - Retrieves flow metadata (nodes, edges) without full flow instance\n * @property runFlow - Starts a new flow execution and returns immediately with job ID\n * @property continueFlow - Resumes a paused flow with new data for a specific node\n * @property getJobStatus - Retrieves current status and results of a flow job\n * @property subscribeToFlowEvents - Subscribes WebSocket to flow execution events\n * @property unsubscribeFromFlowEvents - Unsubscribes from flow events\n *\n * @example\n * ```typescript\n * // Execute a flow\n * const program = Effect.gen(function* () {\n * const server = yield* FlowServer;\n *\n * // Start flow execution (returns immediately)\n * const job = yield* server.runFlow({\n * flowId: \"resize-optimize\",\n * storageId: \"s3-production\",\n * clientId: \"client123\",\n * inputs: {\n * input_1: { uploadId: \"upload_abc123\" }\n * }\n * });\n *\n * // Subscribe to events\n * yield* server.subscribeToFlowEvents(job.id, websocket);\n *\n * // Poll for status\n * const status = yield* server.getJobStatus(job.id);\n * console.log(status.status); // \"running\", \"paused\", \"completed\", or \"failed\"\n *\n * return job;\n * });\n *\n * // Resume a paused flow\n * const resume = Effect.gen(function* () {\n * const server = yield* FlowServer;\n *\n * // Flow paused waiting for user input at node \"approval_1\"\n * const job = yield* server.continueFlow({\n * jobId: \"job123\",\n * nodeId: \"approval_1\",\n * newData: { approved: true },\n * clientId: \"client123\"\n * });\n *\n * return job;\n * });\n *\n * // Check flow structure before execution\n * const inspect = Effect.gen(function* () {\n * const server = yield* FlowServer;\n *\n * const flowData = yield* server.getFlowData(\"resize-optimize\", \"client123\");\n * console.log(\"Nodes:\", flowData.nodes);\n * console.log(\"Edges:\", flowData.edges);\n *\n * return flowData;\n * });\n * ```\n */\nexport type FlowServerShape = {\n getFlow: <TRequirements>(\n flowId: string,\n clientId: string | null,\n ) => Effect.Effect<Flow<any, any, TRequirements>, UploadistaError>;\n\n getFlowData: (\n flowId: string,\n clientId: string | null,\n ) => Effect.Effect<FlowData, UploadistaError>;\n\n runFlow: <TRequirements>({\n flowId,\n storageId,\n clientId,\n inputs,\n }: {\n flowId: string;\n storageId: string;\n clientId: string | null;\n inputs: any;\n }) => Effect.Effect<FlowJob, UploadistaError, TRequirements>;\n\n continueFlow: <TRequirements>({\n jobId,\n nodeId,\n newData,\n clientId,\n }: {\n jobId: string;\n nodeId: string;\n newData: unknown;\n clientId: string | null;\n }) => Effect.Effect<FlowJob, UploadistaError, TRequirements>;\n\n getJobStatus: (jobId: string) => Effect.Effect<FlowJob, UploadistaError>;\n\n subscribeToFlowEvents: (\n jobId: string,\n connection: WebSocketConnection,\n ) => Effect.Effect<void, UploadistaError>;\n\n unsubscribeFromFlowEvents: (\n jobId: string,\n ) => Effect.Effect<void, UploadistaError>;\n};\n\n/**\n * Effect-TS context tag for the FlowServer service.\n *\n * Use this tag to access the FlowServer in an Effect context.\n * The server must be provided via a Layer or dependency injection.\n *\n * @example\n * ```typescript\n * // Access FlowServer in an Effect\n * const flowEffect = Effect.gen(function* () {\n * const server = yield* FlowServer;\n * const job = yield* server.runFlow({\n * flowId: \"my-flow\",\n * storageId: \"s3\",\n * clientId: null,\n * inputs: {}\n * });\n * return job;\n * });\n *\n * // Provide FlowServer layer\n * const program = flowEffect.pipe(\n * Effect.provide(flowServer),\n * Effect.provide(flowProviderLayer),\n * Effect.provide(flowJobKvStore)\n * );\n * ```\n */\nexport class FlowServer extends Context.Tag(\"FlowServer\")<\n FlowServer,\n FlowServerShape\n>() {}\n\n/**\n * Legacy configuration options for FlowServer.\n *\n * @deprecated Use Effect Layers and FlowProvider instead.\n * This type is kept for backward compatibility.\n *\n * @property getFlow - Function to retrieve flow definitions\n * @property kvStore - KV store for flow job metadata\n */\nexport type FlowServerOptions = {\n getFlow: <TRequirements>({\n flowId,\n storageId,\n }: {\n flowId: string;\n storageId: string;\n }) => Promise<Flow<any, any, TRequirements>>;\n kvStore: KvStore<FlowJob>;\n};\n\nconst isResultUploadFile = (result: unknown): result is UploadFile => {\n return typeof result === \"object\" && result !== null && \"id\" in result;\n};\n\n// Function to enhance a flow with event emission capabilities\nfunction withFlowEvents<\n TFlowInputSchema extends z.ZodSchema<any>,\n TFlowOutputSchema extends z.ZodSchema<any>,\n TRequirements,\n>(\n flow: Flow<TFlowInputSchema, TFlowOutputSchema, TRequirements>,\n eventEmitter: EventEmitter<FlowEvent>,\n kvStore: KvStore<FlowJob>,\n): Flow<TFlowInputSchema, TFlowOutputSchema, TRequirements> {\n // Shared helper to create onEvent callback for a given jobId\n const createOnEventCallback = (executionJobId: string) => {\n // Helper to update job in KV store\n const updateJobInStore = (updates: Partial<FlowJob>) =>\n Effect.gen(function* () {\n const job = yield* kvStore.get(executionJobId);\n if (job) {\n yield* kvStore.set(executionJobId, {\n ...job,\n ...updates,\n updatedAt: new Date(),\n });\n }\n });\n\n // Create the onEvent callback that calls original onEvent, emits to eventEmitter, and updates job\n return (event: FlowEvent) =>\n Effect.gen(function* () {\n // Call the original onEvent from the flow if it exists\n // Catch errors to prevent them from blocking flow execution\n if (flow.onEvent) {\n yield* Effect.catchAll(flow.onEvent(event), (error) => {\n // Log the error but don't fail the flow\n Effect.logError(\"Original onEvent failed\", error);\n return Effect.succeed({ eventId: null });\n });\n }\n\n // Emit event\n yield* eventEmitter.emit(executionJobId, event);\n\n Effect.logInfo(\n `Updating job ${executionJobId} with event ${event.eventType}`,\n );\n\n // Update job based on event type\n switch (event.eventType) {\n case EventType.FlowStart:\n yield* updateJobInStore({ status: \"running\" });\n break;\n\n case EventType.FlowEnd:\n // Flow end is handled by executeFlowInBackground\n // This case ensures the event is still emitted\n break;\n\n case EventType.FlowError:\n yield* updateJobInStore({\n status: \"failed\",\n error: event.error,\n });\n break;\n\n case EventType.NodeStart:\n yield* Effect.gen(function* () {\n const job = yield* kvStore.get(executionJobId);\n if (job) {\n const existingTask = job.tasks.find(\n (t) => t.nodeId === event.nodeId,\n );\n const updatedTasks = existingTask\n ? job.tasks.map((t) =>\n t.nodeId === event.nodeId\n ? {\n ...t,\n status: \"running\" as const,\n updatedAt: new Date(),\n }\n : t,\n )\n : [\n ...job.tasks,\n {\n nodeId: event.nodeId,\n status: \"running\" as const,\n createdAt: new Date(),\n updatedAt: new Date(),\n },\n ];\n\n yield* kvStore.set(executionJobId, {\n ...job,\n tasks: updatedTasks,\n updatedAt: new Date(),\n });\n }\n });\n break;\n\n case EventType.NodePause:\n yield* Effect.gen(function* () {\n const job = yield* kvStore.get(executionJobId);\n if (job) {\n const existingTask = job.tasks.find(\n (t) => t.nodeId === event.nodeId,\n );\n const updatedTasks = existingTask\n ? job.tasks.map((t) =>\n t.nodeId === event.nodeId\n ? {\n ...t,\n status: \"paused\" as const,\n result: event.partialData,\n updatedAt: new Date(),\n }\n : t,\n )\n : [\n ...job.tasks,\n {\n nodeId: event.nodeId,\n status: \"paused\" as const,\n result: event.partialData,\n createdAt: new Date(),\n updatedAt: new Date(),\n },\n ];\n\n yield* kvStore.set(executionJobId, {\n ...job,\n tasks: updatedTasks,\n updatedAt: new Date(),\n });\n }\n });\n break;\n\n case EventType.NodeResume:\n yield* Effect.gen(function* () {\n const job = yield* kvStore.get(executionJobId);\n if (job) {\n const updatedTasks = job.tasks.map((t) =>\n t.nodeId === event.nodeId\n ? {\n ...t,\n status: \"running\" as const,\n updatedAt: new Date(),\n }\n : t,\n );\n\n yield* kvStore.set(executionJobId, {\n ...job,\n tasks: updatedTasks,\n updatedAt: new Date(),\n });\n }\n });\n break;\n\n case EventType.NodeEnd:\n yield* Effect.gen(function* () {\n const job = yield* kvStore.get(executionJobId);\n if (job) {\n const updatedTasks = job.tasks.map((t) =>\n t.nodeId === event.nodeId\n ? {\n ...t,\n status: \"completed\" as const,\n result: event.result,\n updatedAt: new Date(),\n }\n : t,\n );\n\n // Track intermediate files for cleanup\n // Check if result is an UploadFile and node is not an output node\n const node = flow.nodes.find((n) => n.id === event.nodeId);\n const isOutputNode = node?.type === \"output\";\n const result = event.result;\n\n let intermediateFiles = job.intermediateFiles || [];\n\n if (isOutputNode && isResultUploadFile(result) && result.id) {\n // If this is an output node and it returns a file that was an intermediate file,\n // remove it from the intermediate files list (it's now the final output)\n intermediateFiles = intermediateFiles.filter(\n (fileId) => fileId !== result.id,\n );\n } else if (\n !isOutputNode &&\n isResultUploadFile(result) &&\n result.id\n ) {\n // Only add to intermediate files if it's not an output node\n if (!intermediateFiles.includes(result.id)) {\n intermediateFiles.push(result.id);\n }\n }\n\n yield* kvStore.set(executionJobId, {\n ...job,\n tasks: updatedTasks,\n intermediateFiles,\n updatedAt: new Date(),\n });\n }\n });\n break;\n\n case EventType.NodeError:\n yield* Effect.gen(function* () {\n const job = yield* kvStore.get(executionJobId);\n if (job) {\n const updatedTasks = job.tasks.map((t) =>\n t.nodeId === event.nodeId\n ? {\n ...t,\n status: \"failed\" as const,\n error: event.error,\n retryCount: event.retryCount,\n updatedAt: new Date(),\n }\n : t,\n );\n\n yield* kvStore.set(executionJobId, {\n ...job,\n tasks: updatedTasks,\n error: event.error,\n updatedAt: new Date(),\n });\n }\n });\n break;\n }\n\n return { eventId: executionJobId };\n });\n };\n\n return {\n ...flow,\n run: (args: {\n inputs?: Record<string, z.infer<TFlowInputSchema>>;\n storageId: string;\n jobId?: string;\n clientId: string | null;\n }) => {\n return Effect.gen(function* () {\n // Use provided jobId or generate a new one\n const executionJobId = args.jobId || crypto.randomUUID();\n\n const onEventCallback = createOnEventCallback(executionJobId);\n\n // Create a new flow with the same configuration but with onEvent callback\n const flowWithEvents = yield* createFlowWithSchema({\n flowId: flow.id,\n name: flow.name,\n nodes: flow.nodes,\n edges: flow.edges,\n inputSchema: flow.inputSchema,\n outputSchema: flow.outputSchema,\n onEvent: onEventCallback,\n });\n\n // Run the enhanced flow with consistent jobId\n const result = yield* flowWithEvents.run({\n ...args,\n jobId: executionJobId,\n clientId: args.clientId,\n });\n\n // Return the result directly (can be completed or paused)\n return result;\n });\n },\n resume: (args: {\n jobId: string;\n storageId: string;\n nodeResults: Record<string, unknown>;\n executionState: {\n executionOrder: string[];\n currentIndex: number;\n inputs: Record<string, z.infer<TFlowInputSchema>>;\n };\n clientId: string | null;\n }) => {\n return Effect.gen(function* () {\n const executionJobId = args.jobId;\n\n const onEventCallback = createOnEventCallback(executionJobId);\n\n // Create a new flow with the same configuration but with onEvent callback\n const flowWithEvents = yield* createFlowWithSchema({\n flowId: flow.id,\n name: flow.name,\n nodes: flow.nodes,\n edges: flow.edges,\n inputSchema: flow.inputSchema,\n outputSchema: flow.outputSchema,\n onEvent: onEventCallback,\n });\n\n // Resume the enhanced flow\n const result = yield* flowWithEvents.resume(args);\n\n // Return the result directly (can be completed or paused)\n return result;\n });\n },\n };\n}\n\n// Core FlowServer implementation\nexport function createFlowServer() {\n return Effect.gen(function* () {\n const flowProvider = yield* FlowProvider;\n const eventEmitter = yield* FlowEventEmitter;\n const kvStore = yield* FlowJobKVStore;\n const uploadServer = yield* UploadServer;\n\n const updateJob = (jobId: string, updates: Partial<FlowJob>) =>\n Effect.gen(function* () {\n const job = yield* kvStore.get(jobId);\n if (!job) {\n return yield* Effect.fail(\n UploadistaError.fromCode(\"FLOW_JOB_NOT_FOUND\", {\n cause: `Job ${jobId} not found`,\n }),\n );\n }\n return yield* kvStore.set(jobId, { ...job, ...updates });\n });\n\n // Helper function to cleanup intermediate files\n const cleanupIntermediateFiles = (jobId: string, clientId: string | null) =>\n Effect.gen(function* () {\n const job = yield* kvStore.get(jobId);\n if (\n !job ||\n !job.intermediateFiles ||\n job.intermediateFiles.length === 0\n ) {\n return;\n }\n\n yield* Effect.logInfo(\n `Cleaning up ${job.intermediateFiles.length} intermediate files for job ${jobId}`,\n );\n\n // Delete each intermediate file\n yield* Effect.all(\n job.intermediateFiles.map((fileId) =>\n Effect.gen(function* () {\n yield* uploadServer.delete(fileId, clientId);\n yield* Effect.logDebug(`Deleted intermediate file ${fileId}`);\n }).pipe(\n Effect.catchAll((error) =>\n Effect.gen(function* () {\n yield* Effect.logWarning(\n `Failed to delete intermediate file ${fileId}: ${error}`,\n );\n return Effect.succeed(undefined);\n }),\n ),\n ),\n ),\n { concurrency: 5 },\n );\n\n // Clear the intermediateFiles array\n yield* updateJob(jobId, {\n intermediateFiles: [],\n });\n });\n\n // Helper function to execute flow in background\n const executeFlowInBackground = ({\n jobId,\n flow,\n storageId,\n clientId,\n inputs,\n }: {\n jobId: string;\n flow: Flow<any, any, any>;\n storageId: string;\n clientId: string | null;\n inputs: Record<string, any>;\n }) =>\n Effect.gen(function* () {\n // Update job status to running\n yield* updateJob(jobId, {\n status: \"running\",\n });\n\n const flowWithEvents = withFlowEvents(flow, eventEmitter, kvStore);\n\n // Run the flow with the consistent jobId\n const result = yield* flowWithEvents.run({\n inputs,\n storageId,\n jobId,\n clientId,\n });\n\n // Handle result based on type\n if (result.type === \"paused\") {\n // Update job as paused (node results are in tasks, not executionState)\n yield* updateJob(jobId, {\n status: \"paused\",\n pausedAt: result.nodeId,\n executionState: result.executionState,\n updatedAt: new Date(),\n });\n } else {\n // Update job as completed with final result\n yield* updateJob(jobId, {\n status: \"completed\",\n result: result.result,\n updatedAt: new Date(),\n endedAt: new Date(),\n });\n\n // Cleanup intermediate files\n yield* cleanupIntermediateFiles(jobId, clientId);\n }\n\n return result;\n }).pipe(\n Effect.catchAll((error) =>\n Effect.gen(function* () {\n yield* Effect.logError(\"Flow execution failed\", error);\n\n // Convert error to a proper message\n const errorMessage =\n error instanceof UploadistaError ? error.body : String(error);\n\n yield* Effect.logInfo(\n `Updating job ${jobId} to failed status with error: ${errorMessage}`,\n );\n\n // Update job as failed - do this FIRST before cleanup\n yield* updateJob(jobId, {\n status: \"failed\",\n error: errorMessage,\n updatedAt: new Date(),\n }).pipe(\n Effect.catchAll((updateError) =>\n Effect.gen(function* () {\n yield* Effect.logError(\n `Failed to update job ${jobId}`,\n updateError,\n );\n return Effect.succeed(undefined);\n }),\n ),\n );\n\n // Emit FlowError event to notify client\n const job = yield* kvStore.get(jobId);\n if (job) {\n yield* eventEmitter\n .emit(jobId, {\n jobId,\n eventType: EventType.FlowError,\n flowId: job.flowId,\n error: errorMessage,\n })\n .pipe(\n Effect.catchAll((emitError) =>\n Effect.gen(function* () {\n yield* Effect.logError(\n `Failed to emit FlowError event for job ${jobId}`,\n emitError,\n );\n return Effect.succeed(undefined);\n }),\n ),\n );\n }\n\n // Cleanup intermediate files even on failure (don't let this fail the error handling)\n yield* cleanupIntermediateFiles(jobId, clientId).pipe(\n Effect.catchAll((cleanupError) =>\n Effect.gen(function* () {\n yield* Effect.logWarning(\n `Failed to cleanup intermediate files for job ${jobId}`,\n cleanupError,\n );\n return Effect.succeed(undefined);\n }),\n ),\n );\n\n return Effect.fail(error);\n }),\n ),\n );\n\n return {\n getFlow: (flowId, clientId) =>\n Effect.gen(function* () {\n const flow = yield* flowProvider.getFlow(flowId, clientId);\n return flow;\n }),\n\n getFlowData: (flowId, clientId) =>\n Effect.gen(function* () {\n const flow = yield* flowProvider.getFlow(flowId, clientId);\n return getFlowData(flow);\n }),\n\n runFlow: ({\n flowId,\n storageId,\n clientId,\n inputs,\n }: {\n flowId: string;\n storageId: string;\n clientId: string | null;\n inputs: unknown;\n }) =>\n Effect.gen(function* () {\n const parsedParams = yield* Effect.try({\n try: () => runArgsSchema.parse({ inputs }),\n catch: (error) =>\n UploadistaError.fromCode(\"FLOW_INPUT_VALIDATION_ERROR\", {\n cause: error,\n }),\n });\n\n // Generate a unique jobId\n const jobId = crypto.randomUUID();\n const createdAt = new Date();\n\n // Store initial job metadata\n const job: FlowJob = {\n id: jobId,\n flowId,\n storageId,\n clientId,\n status: \"started\",\n createdAt,\n updatedAt: createdAt,\n tasks: [],\n };\n\n yield* kvStore.set(jobId, job);\n\n // Get the flow and start background execution\n const flow = yield* flowProvider.getFlow(flowId, clientId);\n\n // Fork the flow execution to run in background as daemon\n yield* Effect.forkDaemon(\n executeFlowInBackground({\n jobId,\n flow,\n storageId,\n clientId,\n inputs: parsedParams.inputs,\n }).pipe(\n Effect.tapErrorCause((cause) =>\n Effect.logError(\"Flow execution failed\", cause),\n ),\n ),\n );\n\n // Return immediately with jobId\n return job;\n }),\n\n getJobStatus: (jobId: string) =>\n Effect.gen(function* () {\n const job = yield* kvStore.get(jobId);\n if (!job) {\n return yield* Effect.fail(\n UploadistaError.fromCode(\"FLOW_JOB_NOT_FOUND\", {\n cause: `Job ${jobId} not found`,\n }),\n );\n }\n\n return job;\n }),\n\n continueFlow: ({\n jobId,\n nodeId,\n newData,\n clientId,\n }: {\n jobId: string;\n nodeId: string;\n newData: unknown;\n clientId: string | null;\n }) =>\n Effect.gen(function* () {\n console.log(\"continueFlow\", jobId, nodeId, newData);\n // Get the current job\n const job = yield* kvStore.get(jobId);\n if (!job) {\n console.error(\"Job not found\");\n return yield* Effect.fail(\n UploadistaError.fromCode(\"FLOW_JOB_NOT_FOUND\", {\n cause: `Job ${jobId} not found`,\n }),\n );\n }\n\n // Verify job is paused\n if (job.status !== \"paused\") {\n console.error(\"Job is not paused\");\n return yield* Effect.fail(\n UploadistaError.fromCode(\"FLOW_JOB_ERROR\", {\n cause: `Job ${jobId} is not paused (status: ${job.status})`,\n }),\n );\n }\n\n // Verify it's paused at the expected node\n if (job.pausedAt !== nodeId) {\n console.error(\"Job is not paused at the expected node\");\n return yield* Effect.fail(\n UploadistaError.fromCode(\"FLOW_JOB_ERROR\", {\n cause: `Job ${jobId} is paused at node ${job.pausedAt}, not ${nodeId}`,\n }),\n );\n }\n\n // Verify we have execution state\n if (!job.executionState) {\n console.error(\"Job has no execution state\");\n return yield* Effect.fail(\n UploadistaError.fromCode(\"FLOW_JOB_ERROR\", {\n cause: `Job ${jobId} has no execution state`,\n }),\n );\n }\n\n // Reconstruct nodeResults from tasks\n const nodeResults = job.tasks.reduce(\n (acc, task) => {\n if (task.result !== undefined) {\n acc[task.nodeId] = task.result;\n }\n return acc;\n },\n {} as Record<string, unknown>,\n );\n\n // Update with new data\n const updatedNodeResults = {\n ...nodeResults,\n [nodeId]: newData,\n };\n\n const updatedInputs = {\n ...job.executionState.inputs,\n [nodeId]: newData,\n };\n\n // Update job status to running BEFORE forking background execution\n // This ensures the status is updated synchronously before events start firing\n yield* updateJob(jobId, {\n status: \"running\",\n });\n\n // Get the flow\n const flow = yield* flowProvider.getFlow(job.flowId, job.clientId);\n\n // Helper to resume flow in background\n const resumeFlowInBackground = Effect.gen(function* () {\n const flowWithEvents = withFlowEvents(flow, eventEmitter, kvStore);\n\n if (!job.executionState) {\n return yield* Effect.fail(\n UploadistaError.fromCode(\"FLOW_JOB_ERROR\", {\n cause: `Job ${jobId} has no execution state`,\n }),\n );\n }\n\n // Resume the flow with updated state\n const result = yield* flowWithEvents.resume({\n jobId,\n storageId: job.storageId,\n nodeResults: updatedNodeResults,\n executionState: {\n ...job.executionState,\n inputs: updatedInputs,\n },\n clientId: job.clientId,\n });\n\n // Handle result based on type\n if (result.type === \"paused\") {\n // Update job as paused again (node results are in tasks, not executionState)\n yield* updateJob(jobId, {\n status: \"paused\",\n pausedAt: result.nodeId,\n executionState: result.executionState,\n updatedAt: new Date(),\n });\n } else {\n // Update job as completed with final result\n yield* updateJob(jobId, {\n status: \"completed\",\n pausedAt: undefined,\n executionState: undefined,\n result: result.result,\n updatedAt: new Date(),\n endedAt: new Date(),\n });\n\n // Cleanup intermediate files\n yield* cleanupIntermediateFiles(jobId, clientId);\n }\n\n return result;\n }).pipe(\n Effect.catchAll((error) =>\n Effect.gen(function* () {\n yield* Effect.logError(\"Flow resume failed\", error);\n\n // Convert error to a proper message\n const errorMessage =\n error instanceof UploadistaError ? error.body : String(error);\n\n yield* Effect.logInfo(\n `Updating job ${jobId} to failed status with error: ${errorMessage}`,\n );\n\n // Update job as failed - do this FIRST before cleanup\n yield* updateJob(jobId, {\n status: \"failed\",\n error: errorMessage,\n updatedAt: new Date(),\n }).pipe(\n Effect.catchAll((updateError) =>\n Effect.gen(function* () {\n yield* Effect.logError(\n `Failed to update job ${jobId}`,\n updateError,\n );\n return Effect.succeed(undefined);\n }),\n ),\n );\n\n // Emit FlowError event to notify client\n const currentJob = yield* kvStore.get(jobId);\n if (currentJob) {\n yield* eventEmitter\n .emit(jobId, {\n jobId,\n eventType: EventType.FlowError,\n flowId: currentJob.flowId,\n error: errorMessage,\n })\n .pipe(\n Effect.catchAll((emitError) =>\n Effect.gen(function* () {\n yield* Effect.logError(\n `Failed to emit FlowError event for job ${jobId}`,\n emitError,\n );\n return Effect.succeed(undefined);\n }),\n ),\n );\n }\n\n // Cleanup intermediate files even on failure (don't let this fail the error handling)\n yield* cleanupIntermediateFiles(jobId, clientId).pipe(\n Effect.catchAll((cleanupError) =>\n Effect.gen(function* () {\n yield* Effect.logWarning(\n `Failed to cleanup intermediate files for job ${jobId}`,\n cleanupError,\n );\n return Effect.succeed(undefined);\n }),\n ),\n );\n\n return Effect.fail(error);\n }),\n ),\n );\n\n // Fork the resume execution to run in background as daemon\n yield* Effect.forkDaemon(\n resumeFlowInBackground.pipe(\n Effect.tapErrorCause((cause) =>\n Effect.logError(\"Flow resume failed\", cause),\n ),\n ),\n );\n\n // Return immediately with updated job\n const updatedJob = yield* kvStore.get(jobId);\n if (!updatedJob) {\n return yield* Effect.fail(\n UploadistaError.fromCode(\"FLOW_JOB_NOT_FOUND\", {\n cause: `Job ${jobId} not found after update`,\n }),\n );\n }\n return updatedJob;\n }),\n\n subscribeToFlowEvents: (jobId: string, connection: WebSocketConnection) =>\n Effect.gen(function* () {\n yield* eventEmitter.subscribe(jobId, connection);\n }),\n\n unsubscribeFromFlowEvents: (jobId: string) =>\n Effect.gen(function* () {\n yield* eventEmitter.unsubscribe(jobId);\n }),\n } satisfies FlowServerShape;\n });\n}\n\n// Export the FlowServer layer with job store dependency\nexport const flowServer = Layer.effect(FlowServer, createFlowServer());\nexport type FlowServerLayer = typeof flowServer;\n","/**\n * Core flow type definitions and node specifications.\n *\n * This module defines the fundamental types for the Flow Engine, including node\n * definitions, execution results, edges, and configuration. These types form the\n * foundation of the DAG processing system.\n *\n * @module flow/types/flow-types\n * @see {@link FlowNode} for node specification\n * @see {@link FlowConfig} for flow configuration\n */\n\n/** biome-ignore-all lint/suspicious/noExplicitAny: any is used to allow for dynamic types */\n\nimport type { Effect } from \"effect\";\nimport type { z } from \"zod\";\nimport type { UploadistaError } from \"../../errors\";\nimport type { FlowEvent, FlowEventFlowEnd, FlowEventFlowStart } from \"../event\";\nimport { NodeType } from \"../node\";\n\n/**\n * Type mapping for node input/output schemas.\n * Used for type-safe node connections in typed flows.\n */\nexport type NodeTypeMap = Record<string, { input: unknown; output: unknown }>;\n\n/**\n * Minimal node data without execution logic.\n * Used for serialization and UI display.\n *\n * @property id - Unique node identifier\n * @property name - Human-readable node name\n * @property description - Explanation of what the node does\n * @property type - Node category (input, transform, conditional, output, etc.)\n */\nexport type FlowNodeData = {\n id: string;\n name: string;\n description: string;\n type: NodeType;\n};\n\n/**\n * Result of a node execution - either complete or waiting for more data.\n *\n * @template TOutput - Type of the node's output data\n *\n * @remarks\n * Nodes can return \"waiting\" to pause flow execution when they need additional\n * data (e.g., chunked uploads, external service responses). The flow can be\n * resumed later with the missing data.\n *\n * @example\n * ```typescript\n * // Node completes immediately\n * return completeNodeExecution({ processedData });\n *\n * // Node waits for more chunks\n * if (needsMoreData) {\n * return waitingNodeExecution({ receivedChunks: 3, totalChunks: 10 });\n * }\n * ```\n */\nexport type NodeExecutionResult<TOutput> =\n | { type: \"complete\"; data: TOutput }\n | { type: \"waiting\"; partialData?: unknown };\n\n/**\n * Helper function to create a complete node execution result.\n *\n * @template TOutput - Type of the output data\n * @param data - The output data from the node\n * @returns A complete execution result\n *\n * @example\n * ```typescript\n * return completeNodeExecution({\n * url: uploadedFile.url,\n * size: uploadedFile.size\n * });\n * ```\n */\nexport const completeNodeExecution = <TOutput>(data: TOutput) => ({\n type: \"complete\" as const,\n data,\n});\n\n/**\n * Helper function to create a waiting node execution result.\n *\n * @param partialData - Optional partial data available so far\n * @returns A waiting execution result that pauses the flow\n *\n * @example\n * ```typescript\n * // Wait for more upload chunks\n * return waitingNodeExecution({\n * receivedBytes: currentSize,\n * totalBytes: expectedSize\n * });\n * ```\n */\nexport const waitingNodeExecution = (partialData?: unknown) => ({\n type: \"waiting\" as const,\n partialData,\n});\n\n/**\n * A flow node represents a single processing step in the DAG.\n *\n * Nodes are the building blocks of flows. Each node has typed inputs/outputs,\n * execution logic, and optional features like conditions, retries, and pausing.\n *\n * @template TInput - Type of data the node accepts\n * @template TOutput - Type of data the node produces\n * @template TError - Type of errors the node can throw\n *\n * @property inputSchema - Zod schema for validating input data\n * @property outputSchema - Zod schema for validating output data\n * @property run - Effect-based execution function\n * @property condition - Optional conditional execution rule\n * @property multiInput - Whether node accepts multiple inputs (default: false)\n * @property multiOutput - Whether node produces multiple outputs (default: false)\n * @property pausable - Whether node can pause execution (default: false)\n * @property retry - Optional retry configuration\n *\n * @remarks\n * - Nodes use Effect for composable error handling and dependency injection\n * - Input/output schemas ensure type safety at runtime\n * - Conditions are evaluated before execution\n * - Retry logic supports exponential backoff\n * - Pausable nodes can halt flow execution and resume later\n *\n * @example\n * ```typescript\n * const resizeNode: FlowNode<InputFile, UploadFile> = {\n * id: \"resize\",\n * name: \"Resize Image\",\n * description: \"Resize image to specified dimensions\",\n * type: NodeType.transform,\n * inputSchema: inputFileSchema,\n * outputSchema: uploadFileSchema,\n * run: ({ data, storageId }) => Effect.gen(function* () {\n * const resized = yield* resizeImage(data, { width: 1920, height: 1080 });\n * return completeNodeExecution(resized);\n * }),\n * retry: {\n * maxRetries: 3,\n * retryDelay: 1000,\n * exponentialBackoff: true\n * }\n * };\n * ```\n *\n * @see {@link NodeExecutionResult} for return type options\n * @see {@link FlowCondition} for conditional execution\n */\nexport type FlowNode<\n TInput = unknown,\n TOutput = unknown,\n TError = UploadistaError,\n> = FlowNodeData & {\n inputSchema: z.ZodSchema<TInput>;\n outputSchema: z.ZodSchema<TOutput>;\n run: (args: {\n data: TInput;\n jobId: string;\n storageId: string;\n flowId: string;\n inputs?: Record<string, unknown>;\n clientId: string | null;\n }) => Effect.Effect<NodeExecutionResult<TOutput>, TError>;\n condition?: {\n field: string;\n operator: string;\n value: unknown;\n };\n multiInput?: boolean;\n multiOutput?: boolean;\n pausable?: boolean; // Flag to indicate this node can pause execution\n retry?: {\n maxRetries?: number; // Maximum number of retry attempts (default: 0)\n retryDelay?: number; // Base delay in ms between retries (default: 1000)\n exponentialBackoff?: boolean; // Use exponential backoff (default: true)\n };\n};\n\n/**\n * Represents a directed edge connecting two nodes in the flow graph.\n *\n * Edges define the data flow direction and can specify ports for multi-input/output nodes.\n *\n * @property source - ID of the source node\n * @property target - ID of the target node\n * @property sourcePort - Optional output port name for multi-output nodes\n * @property targetPort - Optional input port name for multi-input nodes\n *\n * @remarks\n * - Edges must not create cycles (DAG constraint)\n * - Source node's output type should be compatible with target node's input type\n * - Ports allow routing specific outputs to specific inputs\n *\n * @example\n * ```typescript\n * // Simple edge\n * const edge: FlowEdge = {\n * source: \"resize-node\",\n * target: \"optimize-node\"\n * };\n *\n * // Edge with ports (for multiplex nodes)\n * const multiplexEdge: FlowEdge = {\n * source: \"multiplex-node\",\n * target: \"output-node\",\n * sourcePort: \"image\",\n * targetPort: \"primary\"\n * };\n * ```\n */\nexport type FlowEdge = {\n source: string;\n target: string;\n sourcePort?: string; // For multi-output nodes\n targetPort?: string; // For multi-input nodes\n};\n\n/**\n * Function type for checking schema compatibility between nodes.\n *\n * @param from - Source node's output schema\n * @param to - Target node's input schema\n * @returns true if schemas are compatible\n *\n * @remarks\n * Custom type checkers can implement more sophisticated compatibility rules\n * than the default checker.\n *\n * @see {@link FlowTypeValidator} for the default implementation\n */\nexport type TypeCompatibilityChecker = (\n from: z.ZodSchema<any>,\n to: z.ZodSchema<any>,\n) => boolean;\n\n/**\n * Interface for validating node connections and schema compatibility.\n *\n * @remarks\n * Validators ensure that connected nodes have compatible types,\n * preventing runtime type errors in flow execution.\n *\n * @see {@link FlowTypeValidator} for the implementation\n */\nexport type NodeConnectionValidator = {\n validateConnection: (\n sourceNode: FlowNode<any, any>,\n targetNode: FlowNode<any, any>,\n edge: FlowEdge,\n ) => boolean;\n getCompatibleTypes: (\n sourceSchema: z.ZodSchema<any>,\n targetSchema: z.ZodSchema<any>,\n ) => boolean;\n};\n\n/**\n * Configuration object for creating a new flow.\n *\n * FlowConfig defines all aspects of a flow including its nodes, connections,\n * schemas, and optional features like event handlers and parallel execution.\n *\n * @template TFlowInputSchema - Zod schema for flow inputs\n * @template TFlowOutputSchema - Zod schema for flow outputs\n * @template TNodeError - Union of possible errors from node Effects\n * @template TNodeRequirements - Union of requirements from node Effects\n *\n * @property flowId - Unique identifier for the flow\n * @property name - Human-readable flow name\n * @property nodes - Array of nodes (can be plain nodes or Effects resolving to nodes)\n * @property edges - Array of edges connecting the nodes\n * @property inputSchema - Zod schema for validating flow inputs\n * @property outputSchema - Zod schema for validating flow outputs\n * @property typeChecker - Optional custom type compatibility checker\n * @property onEvent - Optional event handler for monitoring execution\n * @property parallelExecution - Optional parallel execution configuration\n *\n * @remarks\n * - Nodes can be provided as plain objects or Effect-wrapped for lazy initialization\n * - Event handlers receive all flow and node events\n * - Parallel execution is experimental and disabled by default\n * - Type checker allows custom schema compatibility rules\n *\n * @example\n * ```typescript\n * const config: FlowConfig<\n * z.ZodObject<{ file: z.ZodType<File> }>,\n * z.ZodType<UploadFile>,\n * never,\n * never\n * > = {\n * flowId: \"image-upload\",\n * name: \"Image Upload Pipeline\",\n * nodes: [inputNode, resizeNode, optimizeNode, storageNode],\n * edges: [\n * { source: \"input\", target: \"resize\" },\n * { source: \"resize\", target: \"optimize\" },\n * { source: \"optimize\", target: \"storage\" }\n * ],\n * inputSchema: z.object({ file: z.instanceof(File) }),\n * outputSchema: uploadFileSchema,\n * onEvent: (event) => Effect.gen(function* () {\n * yield* logEvent(event);\n * return { eventId: event.jobId };\n * })\n * };\n * ```\n *\n * @see {@link createFlowWithSchema} for creating flows from config\n * @see {@link FlowNode} for node specifications\n * @see {@link FlowEdge} for edge specifications\n */\nexport type FlowConfig<\n TFlowInputSchema extends z.ZodSchema<any>,\n TFlowOutputSchema extends z.ZodSchema<any>,\n TNodeError = never,\n TNodeRequirements = never,\n> = {\n flowId: string;\n name: string;\n nodes: Array<\n | FlowNode<any, any, UploadistaError>\n | Effect.Effect<\n FlowNode<any, any, UploadistaError>,\n TNodeError,\n TNodeRequirements\n >\n >;\n edges: FlowEdge[];\n inputSchema: TFlowInputSchema;\n outputSchema: TFlowOutputSchema;\n typeChecker?: TypeCompatibilityChecker;\n onEvent?: (\n event: FlowEvent,\n ) => Effect.Effect<{ eventId: string | null }, UploadistaError>;\n parallelExecution?: {\n enabled?: boolean;\n maxConcurrency?: number;\n };\n};\n\n// Re-export existing types for compatibility\nexport { NodeType };\nexport type { FlowEvent, FlowEventFlowEnd, FlowEventFlowStart };\n","import type { UploadFile } from \"../../types\";\n\ntype FileMetadata = UploadFile[\"metadata\"];\n\nexport type ResolvedUploadMetadata = {\n type: string;\n fileName: string;\n metadata: FileMetadata;\n metadataJson: string | undefined;\n};\n\nexport function resolveUploadMetadata(\n metadata: FileMetadata,\n): ResolvedUploadMetadata {\n if (!metadata) {\n return {\n type: \"\",\n fileName: \"\",\n metadata: undefined,\n metadataJson: undefined,\n };\n }\n\n const normalized = { ...metadata };\n const type = String(\n normalized.type || normalized.mimeType || normalized[\"content-type\"] || \"\"\n );\n if (type) {\n normalized.type ||= type;\n normalized.mimeType ||= type;\n }\n\n const fileName = String(\n normalized.fileName || normalized.originalName || normalized.name || \"\"\n );\n if (fileName) {\n normalized.fileName ||= fileName;\n normalized.originalName ||= fileName;\n normalized.name ||= fileName;\n }\n\n return {\n type,\n fileName,\n metadata: normalized,\n metadataJson: JSON.stringify(normalized),\n };\n}\n","import { Effect } from \"effect\";\nimport { z } from \"zod\";\nimport { UploadistaError } from \"../../errors\";\nimport type { InputFile } from \"../../types\";\nimport { uploadFileSchema } from \"../../types\";\nimport { UploadServer } from \"../../upload\";\nimport { arrayBuffer, fetchFile } from \"../../upload/upload-url\";\nimport { createFlowNode, NodeType } from \"../node\";\nimport { completeNodeExecution, waitingNodeExecution } from \"../types\";\nimport { resolveUploadMetadata } from \"../utils/resolve-upload-metadata\";\n\n/**\n * Schema for initializing a streaming upload operation.\n * Creates a new upload session for chunked file uploads.\n */\nconst initStreamingInputSchema = z.object({\n /** Operation type identifier */\n operation: z.literal(\"init\"),\n /** Storage ID where the file will be stored */\n storageId: z.string(),\n /** Optional metadata for the file */\n metadata: z.record(z.string(), z.any()).optional(),\n});\n\n/**\n * Schema for finalizing a streaming upload operation.\n * Completes the upload process after all chunks have been uploaded.\n */\nconst finalizeStreamingInputSchema = z.object({\n /** Operation type identifier */\n operation: z.literal(\"finalize\"),\n /** Upload ID from the init operation */\n uploadId: z.string(),\n});\n\n/**\n * Schema for fetching a file from a URL.\n * Downloads and processes a file from a remote URL.\n */\nconst urlInputSchema = z.object({\n /** Operation type identifier */\n operation: z.literal(\"url\"),\n /** URL to fetch the file from */\n url: z.string(),\n /** Optional storage ID where the file will be stored */\n storageId: z.string().optional(),\n /** Optional metadata for the file */\n metadata: z.record(z.string(), z.any()).optional(),\n});\n\n/**\n * Union schema for all input operations.\n * Defines the possible input data structures for the input node.\n */\nexport const inputDataSchema = z.union([\n initStreamingInputSchema,\n finalizeStreamingInputSchema,\n urlInputSchema,\n]);\n\n/**\n * Type representing the input data for the input node.\n * Can be one of three operation types: init, finalize, or url.\n */\nexport type InputData = z.infer<typeof inputDataSchema>;\n\n/**\n * Schema for input node filtering parameters.\n * Defines validation rules for incoming files.\n */\nexport const inputNodeParamsSchema = z.object({\n /** Array of allowed MIME types (supports wildcards like \"image/*\") */\n allowedMimeTypes: z.array(z.string()).optional(),\n /** Minimum file size in bytes */\n minSize: z.number().positive().optional(),\n /** Maximum file size in bytes */\n maxSize: z.number().positive().optional(),\n});\n\n/**\n * Parameters for configuring input node validation.\n * Controls which files are accepted based on type and size constraints.\n */\nexport type InputNodeParams = z.infer<typeof inputNodeParamsSchema>;\n\n/**\n * Helper function to validate file against input parameters.\n * Performs MIME type and size validation based on the provided parameters.\n *\n * @param file - File information to validate\n * @param params - Validation parameters\n * @returns An Effect that succeeds if validation passes or fails with validation error\n */\nfunction validateFile(\n file: { type: string; size: number },\n params?: InputNodeParams\n): Effect.Effect<void, UploadistaError> {\n return Effect.gen(function* () {\n if (!params) return;\n\n // Check MIME type\n if (params.allowedMimeTypes && params.allowedMimeTypes.length > 0) {\n const isAllowed = params.allowedMimeTypes.some((allowed) => {\n // Support wildcard patterns like \"image/*\"\n if (allowed.endsWith(\"/*\")) {\n const prefix = allowed.slice(0, -2);\n return file.type.startsWith(prefix);\n }\n return file.type === allowed;\n });\n\n if (!isAllowed) {\n throw yield* UploadistaError.fromCode(\"VALIDATION_ERROR\", {\n cause: new Error(\n `File type \"${\n file.type\n }\" is not allowed. Allowed types: ${params.allowedMimeTypes.join(\n \", \"\n )}`\n ),\n }).toEffect();\n }\n }\n\n // Check minimum size\n if (params.minSize !== undefined && file.size < params.minSize) {\n throw yield* UploadistaError.fromCode(\"VALIDATION_ERROR\", {\n cause: new Error(\n `File size (${file.size} bytes) is below minimum (${params.minSize} bytes)`\n ),\n }).toEffect();\n }\n\n // Check maximum size\n if (params.maxSize !== undefined && file.size > params.maxSize) {\n throw yield* UploadistaError.fromCode(\"VALIDATION_ERROR\", {\n cause: new Error(\n `File size (${file.size} bytes) exceeds maximum (${params.maxSize} bytes)`\n ),\n }).toEffect();\n }\n });\n}\n\n/**\n * Creates an input node for handling file input through multiple methods.\n *\n * The input node supports three operation types:\n * - `init`: Initialize a streaming upload session\n * - `finalize`: Complete a streaming upload after all chunks are uploaded\n * - `url`: Fetch a file directly from a URL\n *\n * @param id - Unique identifier for the node\n * @param params - Optional validation parameters for filtering incoming files\n * @returns An Effect that creates a flow node configured for file input\n *\n * @example\n * ```typescript\n * // Create input node with validation\n * const inputNode = yield* createInputNode(\"file-input\", {\n * allowedMimeTypes: [\"image/*\", \"application/pdf\"],\n * maxSize: 10 * 1024 * 1024, // 10MB\n * });\n *\n * // Create input node without validation\n * const openInputNode = yield* createInputNode(\"open-input\");\n * ```\n */\nexport function createInputNode(id: string, params?: InputNodeParams) {\n return Effect.gen(function* () {\n const uploadServer = yield* UploadServer;\n return yield* createFlowNode({\n id,\n name: \"Input\",\n description:\n \"Handles file input through multiple methods - streaming upload (init/finalize) or direct URL fetch\",\n type: NodeType.input,\n inputSchema: inputDataSchema,\n outputSchema: uploadFileSchema,\n run: ({ data, flowId, jobId, clientId }) => {\n return Effect.gen(function* () {\n switch (data.operation) {\n case \"init\": {\n // Create upload using upload server - it handles all state management\n const inputFile: InputFile = {\n storageId: data.storageId,\n size: data.metadata?.size || 0,\n type: data.metadata?.mimeType || \"application/octet-stream\",\n fileName: data.metadata?.originalName,\n lastModified: data.metadata?.size ? Date.now() : undefined,\n metadata: data.metadata\n ? JSON.stringify(data.metadata)\n : undefined,\n flow: {\n flowId,\n nodeId: id,\n jobId,\n },\n };\n\n const uploadFile = yield* uploadServer.createUpload(\n inputFile,\n clientId\n );\n\n // Return waiting state with the upload file\n // Client will upload chunks directly to the upload API\n return waitingNodeExecution(uploadFile);\n }\n\n case \"finalize\": {\n // Get final upload file from upload server's KV store\n const finalUploadFile = yield* uploadServer.getUpload(\n data.uploadId\n );\n\n // Extract type and size from metadata for validation\n const { type } = resolveUploadMetadata(finalUploadFile.metadata);\n const size = finalUploadFile.size || 0;\n\n // Validate file against params\n yield* validateFile({ type, size }, params);\n\n // Complete the node execution with the final upload file\n // Flow can now continue to next nodes (e.g., save to storage, optimize)\n return completeNodeExecution(finalUploadFile);\n }\n\n case \"url\": {\n // Fetch file from URL directly\n const response = yield* fetchFile(data.url);\n const buffer = yield* arrayBuffer(response);\n\n // Extract metadata from response or use provided metadata\n const mimeType =\n data.metadata?.mimeType ||\n response.headers.get(\"content-type\") ||\n \"application/octet-stream\";\n const size =\n data.metadata?.size ||\n Number(response.headers.get(\"content-length\") || 0);\n const fileName =\n data.metadata?.originalName ||\n data.url.split(\"/\").pop() ||\n \"file\";\n\n // Validate file against params\n yield* validateFile({ type: mimeType, size }, params);\n\n // Create a readable stream from the buffer\n const stream = new ReadableStream({\n start(controller) {\n controller.enqueue(new Uint8Array(buffer));\n controller.close();\n },\n });\n\n // Use upload server to create and store the file\n const inputFile: InputFile = {\n storageId: data.storageId || \"buffer\",\n size,\n type: mimeType,\n fileName,\n lastModified: Date.now(),\n metadata: data.metadata\n ? JSON.stringify(data.metadata)\n : undefined,\n };\n\n const uploadFile = yield* uploadServer.upload(\n inputFile,\n clientId,\n stream\n );\n\n // Complete the node execution with the upload file\n return completeNodeExecution({\n ...uploadFile,\n flow: {\n flowId,\n nodeId: id,\n jobId,\n },\n });\n }\n\n default:\n throw yield* UploadistaError.fromCode(\"VALIDATION_ERROR\", {\n cause: new Error(\"Invalid operation\"),\n }).toEffect();\n }\n });\n },\n });\n });\n}\n","import { Effect } from \"effect\";\nimport { z } from \"zod\";\nimport { UploadistaError } from \"../../errors\";\nimport { type UploadFile, uploadFileSchema } from \"../../types\";\nimport { UploadServer } from \"../../upload\";\nimport { createFlowNode, NodeType } from \"../node\";\nimport { completeNodeExecution } from \"../types\";\nimport { resolveUploadMetadata } from \"../utils/resolve-upload-metadata\";\n\n/**\n * Schema for storage node parameters.\n * Currently empty but can be extended for storage-specific configuration.\n */\nexport const storageParamsSchema = z.object({});\n\n/**\n * Parameters for the storage node.\n * Currently no parameters are required, but the schema is available for future extensions.\n */\nexport type StorageParams = z.infer<typeof storageParamsSchema>;\n\n/**\n * Creates a storage node for storing files in the specified storage.\n *\n * The storage node handles the process of:\n * 1. Reading the input file from the upload server\n * 2. Checking if the file is already in the target storage\n * 3. If not, transferring the file to the target storage\n * 4. Applying optional post-processing\n * 5. Returning the final stored file\n *\n * @param id - Unique identifier for the node\n * @param postProcessFile - Optional function to process the file after storage\n * @returns An Effect that creates a flow node configured for file storage\n *\n * @example\n * ```typescript\n * // Create basic storage node\n * const storageNode = yield* createStorageNode(\"store-file\");\n *\n * // Create storage node with post-processing\n * const storageWithProcessing = yield* createStorageNode(\"store-and-process\", (file) => {\n * return Effect.succeed({\n * ...file,\n * metadata: { ...file.metadata, processed: true }\n * });\n * });\n * ```\n */\nexport function createStorageNode(\n id: string,\n postProcessFile: (file: UploadFile) => Effect.Effect<UploadFile> = (file) =>\n Effect.succeed(file)\n) {\n return Effect.gen(function* () {\n const uploadServer = yield* UploadServer;\n return yield* createFlowNode({\n id,\n name: \"Storage\",\n description: \"Stores a file in the storage\",\n type: NodeType.output,\n inputSchema: uploadFileSchema,\n outputSchema: uploadFileSchema,\n run: ({ data: file, storageId, flowId, jobId, clientId }) => {\n return Effect.gen(function* () {\n const { type, fileName, metadata, metadataJson } =\n resolveUploadMetadata(file.metadata);\n const flow = {\n flowId,\n nodeId: id,\n jobId,\n };\n const normalizedFile = metadata ? { ...file, metadata } : file;\n\n const upload = yield* uploadServer.getUpload(file.id);\n if (!upload.id) {\n return yield* Effect.fail(\n UploadistaError.fromCode(\n \"FILE_READ_ERROR\",\n new Error(\"Upload Key is undefined\")\n )\n );\n }\n // If the upload is already in the correct storage, return the file, just update the flow\n if (upload.storage.id === storageId) {\n return completeNodeExecution(\n yield* postProcessFile({ ...normalizedFile, flow })\n );\n }\n\n const inputBytes = yield* uploadServer.read(file.id, clientId);\n const stream = new ReadableStream({\n start(controller) {\n controller.enqueue(inputBytes);\n controller.close();\n },\n });\n\n const uploadResult = yield* uploadServer.upload(\n {\n storageId,\n size: inputBytes.byteLength,\n type,\n fileName,\n lastModified: 0,\n metadata: metadataJson,\n flow,\n },\n clientId,\n stream\n );\n\n const resolvedUploadResult = resolveUploadMetadata(\n uploadResult.metadata\n );\n\n const postProcessed = yield* postProcessFile(\n resolvedUploadResult.metadata\n ? { ...uploadResult, metadata: resolvedUploadResult.metadata }\n : uploadResult\n );\n\n return completeNodeExecution(postProcessed);\n });\n },\n });\n });\n}\n","import { Effect } from \"effect\";\nimport type { UploadistaError } from \"../../errors\";\nimport type { UploadFile } from \"../../types\";\nimport { uploadFileSchema } from \"../../types\";\nimport { UploadServer } from \"../../upload\";\nimport { createFlowNode, NodeType } from \"../node\";\nimport { completeNodeExecution } from \"../types\";\nimport { resolveUploadMetadata } from \"../utils/resolve-upload-metadata\";\n\n/**\n * Configuration object for creating a transform node.\n */\nexport interface TransformNodeConfig {\n /** Unique identifier for the node */\n id: string;\n /** Human-readable name for the node */\n name: string;\n /** Description of what the node does */\n description: string;\n /** Function that transforms file bytes */\n transform: (\n bytes: Uint8Array,\n file: UploadFile\n ) => Effect.Effect<\n Uint8Array | { bytes: Uint8Array; type?: string; fileName?: string },\n UploadistaError\n >;\n}\n\n/**\n * Creates a transform node that handles the common pattern of:\n * 1. Reading bytes from an UploadFile\n * 2. Transforming the bytes\n * 3. Uploading the result as a new UploadFile\n *\n * This simplifies nodes that just need to transform file bytes without\n * worrying about upload server interactions.\n *\n * @param config - Configuration object for the transform node\n * @returns An Effect that creates a flow node configured for file transformation\n *\n * @example\n * ```typescript\n * // Create an image resize transform node\n * const resizeNode = yield* createTransformNode({\n * id: \"resize-image\",\n * name: \"Resize Image\",\n * description: \"Resizes images to specified dimensions\",\n * transform: (bytes, file) => {\n * // Your transformation logic here\n * return Effect.succeed(transformedBytes);\n * }\n * });\n *\n * // Create a transform node that changes file metadata\n * const metadataTransformNode = yield* createTransformNode({\n * id: \"add-metadata\",\n * name: \"Add Metadata\",\n * description: \"Adds custom metadata to files\",\n * transform: (bytes, file) => {\n * return Effect.succeed({\n * bytes,\n * type: \"application/custom\",\n * fileName: `processed-${file.fileName}`\n * });\n * }\n * });\n * ```\n */\nexport function createTransformNode({\n id,\n name,\n description,\n transform,\n}: TransformNodeConfig) {\n return Effect.gen(function* () {\n const uploadServer = yield* UploadServer;\n\n return yield* createFlowNode({\n id,\n name,\n description,\n type: NodeType.process,\n inputSchema: uploadFileSchema,\n outputSchema: uploadFileSchema,\n run: ({ data: file, storageId, flowId, jobId, clientId }) => {\n return Effect.gen(function* () {\n const flow = {\n flowId,\n nodeId: id,\n jobId,\n };\n // Read input bytes from upload server\n const inputBytes = yield* uploadServer.read(file.id, clientId);\n\n // Transform the bytes using the provided function\n const transformResult = yield* transform(inputBytes, file);\n\n // Handle both simple Uint8Array and object with metadata\n const outputBytes =\n transformResult instanceof Uint8Array\n ? transformResult\n : transformResult.bytes;\n\n const outputType =\n transformResult instanceof Uint8Array\n ? undefined\n : transformResult.type;\n\n const outputFileName =\n transformResult instanceof Uint8Array\n ? undefined\n : transformResult.fileName;\n\n // Create a stream from the output bytes\n const stream = new ReadableStream({\n start(controller) {\n controller.enqueue(outputBytes);\n controller.close();\n },\n });\n\n const { type, fileName, metadata, metadataJson } =\n resolveUploadMetadata(file.metadata);\n\n // Upload the transformed bytes back to the upload server\n // Use output metadata if provided, otherwise fall back to original\n const result = yield* uploadServer.upload(\n {\n storageId,\n size: outputBytes.byteLength,\n type: outputType ?? type,\n fileName: outputFileName ?? fileName,\n lastModified: 0,\n metadata: metadataJson,\n flow,\n },\n clientId,\n stream\n );\n\n return completeNodeExecution(\n metadata\n ? {\n ...result,\n metadata,\n }\n : result\n );\n });\n },\n });\n });\n}\n","import { Context, type Effect } from \"effect\";\nimport type { UploadistaError } from \"@/errors\";\n\n/**\n * Shape definition for the Credential Provider interface.\n * Defines the contract for retrieving credentials for various services.\n */\nexport interface CredentialProviderShape {\n /**\n * Retrieves credentials for a specific service and client.\n *\n * @param params - Parameters for credential retrieval\n * @param params.clientId - Unique identifier for the client, or null if not available\n * @param params.serviceType - Optional service type to get specific credentials for\n * @returns An Effect that resolves to a record of credential key-value pairs\n * @throws {UploadistaError} When credential retrieval fails\n */\n getCredential: (params: {\n clientId: string | null;\n serviceType?: string;\n }) => Effect.Effect<Record<string, unknown>, UploadistaError>;\n}\n\n/**\n * Context tag for the Credential Provider.\n *\n * This tag provides a type-safe way to access credential functionality\n * throughout the application using Effect's dependency injection system.\n *\n * @example\n * ```typescript\n * import { CredentialProvider } from \"@uploadista/core/flow/plugins\";\n *\n * // In your flow node\n * const program = Effect.gen(function* () {\n * const credentialProvider = yield* CredentialProvider;\n * const credentials = yield* credentialProvider.getCredential({\n * clientId: \"user123\",\n * serviceType: \"replicate\"\n * });\n * return credentials;\n * });\n * ```\n */\nexport class CredentialProvider extends Context.Tag(\"CredentialProvider\")<\n CredentialProvider,\n CredentialProviderShape\n>() {}\n","import type { UploadistaError } from \"@uploadista/core/errors\";\nimport { Context, type Effect } from \"effect\";\n\n/**\n * Context information for AI image processing operations.\n * Contains client identification for tracking and billing purposes.\n */\nexport type ImageAiContext = {\n /** Unique identifier for the client making the request, or null if not available */\n clientId: string | null;\n};\n\n/**\n * Shape definition for the Image AI Plugin interface.\n * Defines the contract that all image AI implementations must follow.\n */\nexport type ImageAiPluginShape = {\n /**\n * Removes the background from an image using AI processing.\n *\n * @param inputUrl - The URL of the input image to process\n * @param context - Context information including client ID for tracking\n * @returns An Effect that resolves to an object containing the output image URL\n * @throws {UploadistaError} When the background removal fails\n */\n removeBackground: (\n inputUrl: string,\n context: ImageAiContext\n ) => Effect.Effect<{ outputUrl: string }, UploadistaError>;\n\n /**\n * Generates a textual description of an image using AI analysis.\n *\n * @param inputUrl - The URL of the input image to analyze\n * @param context - Context information including client ID for tracking\n * @returns An Effect that resolves to an object containing the image description\n * @throws {UploadistaError} When the image analysis fails\n */\n describeImage: (\n inputUrl: string,\n context: ImageAiContext\n ) => Effect.Effect<{ description: string }, UploadistaError>;\n};\n\n/**\n * Context tag for the Image AI Plugin.\n *\n * This tag provides a type-safe way to access image AI functionality\n * throughout the application using Effect's dependency injection system.\n *\n * @example\n * ```typescript\n * import { ImageAiPlugin } from \"@uploadista/core/flow/plugins\";\n *\n * // In your flow node\n * const program = Effect.gen(function* () {\n * const imageAi = yield* ImageAiPlugin;\n * const result = yield* imageAi.removeBackground(imageUrl, { clientId: \"user123\" });\n * return result.outputUrl;\n * });\n * ```\n */\nexport class ImageAiPlugin extends Context.Tag(\"ImageAiPlugin\")<\n ImageAiPlugin,\n ImageAiPluginShape\n>() {}\n","import type { UploadistaError } from \"@uploadista/core/errors\";\nimport { Context, type Effect } from \"effect\";\nimport type { OptimizeParams } from \"./types/optimize-node\";\nimport type { ResizeParams } from \"./types/resize-node\";\n\n/**\n * Shape definition for the Image Plugin interface.\n * Defines the contract that all image processing implementations must follow.\n */\nexport type ImagePluginShape = {\n /**\n * Optimizes an image by adjusting quality and format.\n *\n * @param input - The input image as a Uint8Array\n * @param options - Optimization parameters including quality and format\n * @returns An Effect that resolves to the optimized image as a Uint8Array\n * @throws {UploadistaError} When image optimization fails\n */\n optimize: (\n input: Uint8Array,\n options: OptimizeParams\n ) => Effect.Effect<Uint8Array, UploadistaError>;\n \n /**\n * Resizes an image to specified dimensions.\n *\n * @param input - The input image as a Uint8Array\n * @param options - Resize parameters including width, height, and fit mode\n * @returns An Effect that resolves to the resized image as a Uint8Array\n * @throws {UploadistaError} When image resizing fails\n */\n resize: (\n input: Uint8Array,\n options: ResizeParams\n ) => Effect.Effect<Uint8Array, UploadistaError>;\n};\n\n/**\n * Context tag for the Image Plugin.\n *\n * This tag provides a type-safe way to access image processing functionality\n * throughout the application using Effect's dependency injection system.\n *\n * @example\n * ```typescript\n * import { ImagePlugin } from \"@uploadista/core/flow/plugins\";\n *\n * // In your flow node\n * const program = Effect.gen(function* () {\n * const imagePlugin = yield* ImagePlugin;\n * const optimized = yield* imagePlugin.optimize(imageData, { quality: 80, format: \"webp\" });\n * const resized = yield* imagePlugin.resize(optimized, { width: 800, height: 600, fit: \"cover\" });\n * return resized;\n * });\n * ```\n */\nexport class ImagePlugin extends Context.Tag(\"ImagePlugin\")<\n ImagePlugin,\n ImagePluginShape\n>() {}\n","import { z } from \"zod\";\n\n/**\n * Zod schema for validating describe image node parameters.\n * Defines the structure and validation rules for image description requests.\n */\nexport const describeImageParamsSchema = z.object({\n /** Optional service type to use for image description (currently supports \"replicate\") */\n serviceType: z.enum([\"replicate\"]).optional(),\n});\n\n/**\n * Parameters for the describe image node.\n * Controls which AI service to use for generating image descriptions.\n */\nexport type DescribeImageParams = z.infer<typeof describeImageParamsSchema>;\n","import { z } from \"zod\";\n\n/**\n * Zod schema for validating image optimization parameters.\n * Defines the structure and validation rules for image optimization requests.\n */\nexport const optimizeParamsSchema = z.object({\n /** Image quality as a percentage (0-100) */\n quality: z.number().min(0).max(100),\n /** Output image format */\n format: z.enum([\"jpeg\", \"webp\", \"png\", \"avif\"] as const),\n});\n\n/**\n * Parameters for the image optimization node.\n * Controls quality and format settings for image optimization.\n */\nexport type OptimizeParams = z.infer<typeof optimizeParamsSchema>;\n","import { z } from \"zod\";\n\n/**\n * Zod schema for validating remove background node parameters.\n * Defines the structure and validation rules for background removal requests.\n */\nexport const removeBackgroundParamsSchema = z.object({\n /** Optional service type to use for background removal (currently supports \"replicate\") */\n serviceType: z.enum([\"replicate\"]).optional(),\n});\n\n/**\n * Parameters for the remove background node.\n * Controls which AI service to use for background removal processing.\n */\nexport type RemoveBackgroundParams = z.infer<\n typeof removeBackgroundParamsSchema\n>;\n","import { z } from \"zod\";\n\n/**\n * Zod schema for validating image resize parameters.\n * Defines the structure and validation rules for image resizing requests.\n * Requires at least one dimension (width or height) to be specified.\n */\nexport const resizeParamsSchema = z\n .object({\n /** Target width in pixels (optional) */\n width: z.number().positive().optional(),\n /** Target height in pixels (optional) */\n height: z.number().positive().optional(),\n /** How the image should fit within the specified dimensions */\n fit: z.enum([\"contain\", \"cover\", \"fill\"]),\n })\n .refine(\n (data) => data.width || data.height,\n \"Either width or height must be specified for resize\"\n );\n\n/**\n * Parameters for the image resize node.\n * Controls the target dimensions and fitting behavior for image resizing.\n */\nexport type ResizeParams = z.infer<typeof resizeParamsSchema>;\n","import type { UploadistaError } from \"@uploadista/core/errors\";\nimport { Context, type Effect } from \"effect\";\nimport type { UploadFile } from \"@/types\";\n\n/**\n * Parameters for creating a ZIP archive.\n */\nexport type ZipParams = {\n /** Name of the ZIP file to create */\n zipName: string;\n /** Whether to include file metadata in the ZIP archive */\n includeMetadata: boolean;\n};\n\n/**\n * Input data structure for ZIP operations.\n * Represents a single file to be included in the ZIP archive.\n */\nexport type ZipInput = {\n /** Unique identifier for the file */\n id: string;\n /** Binary data of the file */\n data: Uint8Array;\n /** File metadata including name, size, type, etc. */\n metadata: UploadFile[\"metadata\"];\n};\n\n/**\n * Shape definition for the ZIP Plugin interface.\n * Defines the contract that all ZIP implementations must follow.\n */\nexport type ZipPluginShape = {\n /**\n * Creates a ZIP archive from multiple input files.\n *\n * @param inputs - Array of files to include in the ZIP archive\n * @param options - Configuration options for the ZIP creation\n * @returns An Effect that resolves to the ZIP file as a Uint8Array\n * @throws {UploadistaError} When ZIP creation fails\n */\n zip: (\n inputs: ZipInput[],\n options: ZipParams\n ) => Effect.Effect<Uint8Array, UploadistaError>;\n // unzip: (input: ZipInput) => Effect.Effect<Uint8Array, UploadistaError>;\n};\n\n/**\n * Context tag for the ZIP Plugin.\n *\n * This tag provides a type-safe way to access ZIP functionality\n * throughout the application using Effect's dependency injection system.\n *\n * @example\n * ```typescript\n * import { ZipPlugin } from \"@uploadista/core/flow/plugins\";\n *\n * // In your flow node\n * const program = Effect.gen(function* () {\n * const zipPlugin = yield* ZipPlugin;\n * const zipData = yield* zipPlugin.zip(files, { zipName: \"archive.zip\", includeMetadata: true });\n * return zipData;\n * });\n * ```\n */\nexport class ZipPlugin extends Context.Tag(\"ZipPlugin\")<\n ZipPlugin,\n ZipPluginShape\n>() {}\n","/**\n * biome-ignore-all lint/suspicious/noExplicitAny: broadly-typed generics require runtime schema placeholders\n */\nimport { Effect } from \"effect\";\nimport { z } from \"zod\";\nimport type { UploadistaError as CoreUploadistaError } from \"../errors\";\nimport { UploadistaError } from \"../errors\";\nimport type { UploadServer } from \"../upload\";\nimport type { FlowEvent } from \"./event\";\nimport type { Flow, FlowExecutionResult } from \"./flow\";\nimport { createFlowWithSchema } from \"./flow\";\nimport { NodeType } from \"./node\";\nimport type {\n FlowEdge,\n FlowNode,\n TypeCompatibilityChecker,\n} from \"./types/flow-types\";\n\nexport type NodeDefinition<TNodeError = never, TNodeRequirements = never> =\n | FlowNode<any, any, CoreUploadistaError>\n | Effect.Effect<\n FlowNode<any, any, CoreUploadistaError>,\n TNodeError,\n TNodeRequirements\n >;\n\nexport type NodeDefinitionsRecord = Record<string, NodeDefinition<any, any>>;\n\ntype NodeDefinitionError<T> = T extends Effect.Effect<\n FlowNode<any, any, CoreUploadistaError>,\n infer TError,\n any\n>\n ? TError\n : never;\n\ntype NodeDefinitionRequirements<T> = T extends Effect.Effect<\n FlowNode<any, any, CoreUploadistaError>,\n any,\n infer TRequirements\n>\n ? TRequirements\n : never;\n\ntype NodesErrorUnion<TNodes extends NodeDefinitionsRecord> = {\n [K in keyof TNodes]: NodeDefinitionError<TNodes[K]>;\n}[keyof TNodes];\n\ntype NodesRequirementsUnion<TNodes extends NodeDefinitionsRecord> = {\n [K in keyof TNodes]: NodeDefinitionRequirements<TNodes[K]>;\n}[keyof TNodes];\n\nexport type FlowRequirements<TNodes extends NodeDefinitionsRecord> =\n NodesRequirementsUnion<TNodes>;\n\nexport type FlowPluginRequirements<TNodes extends NodeDefinitionsRecord> =\n Exclude<FlowRequirements<TNodes>, UploadServer>;\n\ntype InferNode<T> = T extends FlowNode<any, any, CoreUploadistaError>\n ? T\n : T extends Effect.Effect<infer R, any, any>\n ? R extends FlowNode<any, any, CoreUploadistaError>\n ? R\n : never\n : never;\n\ntype ResolvedNodesRecord<TNodes extends NodeDefinitionsRecord> = {\n [K in keyof TNodes]: InferNode<TNodes[K]>;\n};\n\ntype ExtractKeysByNodeType<\n TNodes extends NodeDefinitionsRecord,\n TType extends NodeType,\n> = {\n [K in keyof TNodes]: InferNode<TNodes[K]>[\"type\"] extends TType ? K : never;\n}[keyof TNodes];\n\ntype SchemaInfer<T> = T extends z.ZodTypeAny ? z.infer<T> : never;\n\nexport type FlowInputMap<TNodes extends NodeDefinitionsRecord> = {\n [K in Extract<\n ExtractKeysByNodeType<TNodes, NodeType.input>,\n string\n >]: SchemaInfer<InferNode<TNodes[K]>[\"inputSchema\"]>;\n};\n\nexport type FlowOutputMap<TNodes extends NodeDefinitionsRecord> = {\n [K in Extract<\n ExtractKeysByNodeType<TNodes, NodeType.output>,\n string\n >]: SchemaInfer<InferNode<TNodes[K]>[\"outputSchema\"]>;\n};\n\ntype FlowInputUnion<TNodes extends NodeDefinitionsRecord> = {\n [K in Extract<\n ExtractKeysByNodeType<TNodes, NodeType.input>,\n string\n >]: SchemaInfer<InferNode<TNodes[K]>[\"inputSchema\"]>;\n}[Extract<ExtractKeysByNodeType<TNodes, NodeType.input>, string>];\n\ntype FlowOutputUnion<TNodes extends NodeDefinitionsRecord> = {\n [K in Extract<\n ExtractKeysByNodeType<TNodes, NodeType.output>,\n string\n >]: SchemaInfer<InferNode<TNodes[K]>[\"outputSchema\"]>;\n}[Extract<ExtractKeysByNodeType<TNodes, NodeType.output>, string>];\n\ntype NodeKey<TNodes extends NodeDefinitionsRecord> = Extract<\n keyof TNodes,\n string\n>;\n\nexport type TypedFlowEdge<TNodes extends NodeDefinitionsRecord> = {\n source: NodeKey<TNodes>;\n target: NodeKey<TNodes>;\n sourcePort?: string;\n targetPort?: string;\n};\n\nexport type TypedFlowConfig<TNodes extends NodeDefinitionsRecord> = {\n flowId: string;\n name: string;\n nodes: TNodes;\n edges: Array<TypedFlowEdge<TNodes>>;\n typeChecker?: TypeCompatibilityChecker;\n onEvent?: (\n event: FlowEvent,\n ) => Effect.Effect<{ eventId: string | null }, CoreUploadistaError>;\n parallelExecution?: {\n enabled?: boolean;\n maxConcurrency?: number;\n };\n inputSchema?: z.ZodTypeAny;\n outputSchema?: z.ZodTypeAny;\n};\n\ndeclare const typedFlowInputsSymbol: unique symbol;\ndeclare const typedFlowOutputsSymbol: unique symbol;\ndeclare const typedFlowPluginsSymbol: unique symbol;\n\nexport type TypedFlow<\n TNodes extends NodeDefinitionsRecord,\n TInputSchema extends z.ZodTypeAny,\n TOutputSchema extends z.ZodTypeAny,\n> = Flow<TInputSchema, TOutputSchema, FlowRequirements<TNodes>> & {\n run: (args: {\n inputs?: Partial<FlowInputMap<TNodes>>;\n storageId: string;\n jobId: string;\n }) => Effect.Effect<\n FlowExecutionResult<FlowOutputMap<TNodes>>,\n CoreUploadistaError,\n FlowRequirements<TNodes>\n >;\n resume: (args: {\n jobId: string;\n storageId: string;\n nodeResults: Record<string, unknown>;\n executionState: {\n executionOrder: string[];\n currentIndex: number;\n inputs: Partial<FlowInputMap<TNodes>>;\n };\n }) => Effect.Effect<\n FlowExecutionResult<FlowOutputMap<TNodes>>,\n CoreUploadistaError,\n FlowRequirements<TNodes>\n >;\n readonly [typedFlowInputsSymbol]?: FlowInputMap<TNodes>;\n readonly [typedFlowOutputsSymbol]?: FlowOutputMap<TNodes>;\n readonly [typedFlowPluginsSymbol]?: FlowPluginRequirements<TNodes>;\n};\n\nconst buildUnionSchema = (\n schemas: z.ZodTypeAny[],\n fallback: z.ZodTypeAny,\n): z.ZodTypeAny => {\n if (schemas.length === 0) {\n return fallback;\n }\n\n const [first, ...rest] = schemas as [z.ZodTypeAny, ...z.ZodTypeAny[]];\n return rest.reduce<z.ZodTypeAny>(\n (acc, schema) => z.union([acc, schema]),\n first,\n );\n};\n\nexport function createFlow<TNodes extends NodeDefinitionsRecord>(\n config: TypedFlowConfig<TNodes>,\n): Effect.Effect<\n TypedFlow<\n TNodes,\n z.ZodType<FlowInputUnion<TNodes>>,\n z.ZodType<FlowOutputUnion<TNodes>>\n >,\n NodesErrorUnion<TNodes> | UploadistaError,\n FlowRequirements<TNodes>\n> {\n return Effect.gen(function* () {\n const nodeEntries = Object.entries(config.nodes) as Array<\n [NodeKey<TNodes>, NodeDefinition]\n >;\n\n const resolveNode = (\n node: NodeDefinition,\n ): Effect.Effect<\n FlowNode<any, any, CoreUploadistaError>,\n NodesErrorUnion<TNodes>,\n FlowRequirements<TNodes>\n > =>\n Effect.isEffect(node)\n ? (node as Effect.Effect<\n FlowNode<any, any, CoreUploadistaError>,\n NodesErrorUnion<TNodes>,\n FlowRequirements<TNodes>\n >)\n : Effect.succeed(node as FlowNode<any, any, CoreUploadistaError>);\n\n const resolvedEntries = yield* Effect.forEach(nodeEntries, ([key, node]) =>\n Effect.flatMap(resolveNode(node), (resolvedNode) => {\n if (resolvedNode.id !== key) {\n return Effect.fail(\n UploadistaError.fromCode(\"FLOW_NODE_ERROR\", {\n cause: new Error(\n `Node key ${key} does not match node id ${resolvedNode.id}`,\n ),\n }),\n );\n }\n return Effect.succeed([key, resolvedNode] as const);\n }),\n );\n\n const resolvedRecord = Object.fromEntries(\n resolvedEntries,\n ) as ResolvedNodesRecord<TNodes>;\n const resolvedNodes = resolvedEntries.map(([, node]) => node);\n\n const inputSchemas = resolvedEntries\n .filter(([, node]) => node.type === NodeType.input)\n .map(([, node]) => node.inputSchema);\n\n const outputSchemas = resolvedEntries\n .filter(([, node]) => node.type === NodeType.output)\n .map(([, node]) => node.outputSchema);\n\n const inputSchema =\n config.inputSchema ?? buildUnionSchema(inputSchemas, z.unknown());\n\n const outputSchema =\n config.outputSchema ?? buildUnionSchema(outputSchemas, z.unknown());\n\n const flowEdges: FlowEdge[] = config.edges.map((edge) => ({\n source: resolvedRecord[edge.source]?.id ?? edge.source,\n target: resolvedRecord[edge.target]?.id ?? edge.target,\n sourcePort: edge.sourcePort,\n targetPort: edge.targetPort,\n }));\n\n const flow = yield* createFlowWithSchema({\n flowId: config.flowId,\n name: config.name,\n nodes: resolvedNodes,\n edges: flowEdges,\n inputSchema,\n outputSchema,\n typeChecker: config.typeChecker,\n onEvent: config.onEvent,\n parallelExecution: config.parallelExecution,\n });\n\n return flow as unknown as TypedFlow<\n TNodes,\n z.ZodType<FlowInputUnion<TNodes>>,\n z.ZodType<FlowOutputUnion<TNodes>>\n >;\n });\n}\n","/**\n * Flow execution argument schemas and types.\n *\n * Defines and validates the arguments passed when running a flow,\n * ensuring inputs are properly structured before execution begins.\n *\n * @module flow/types/run-args\n */\n\nimport { z } from \"zod\";\n\n/**\n * Zod schema for validating flow run arguments.\n *\n * @property inputs - Record mapping input node IDs to their input data\n *\n * @example\n * ```typescript\n * const args = {\n * inputs: {\n * \"input-node-1\": { file: myFile, metadata: { ... } },\n * \"input-node-2\": { file: anotherFile }\n * }\n * };\n *\n * // Validate before running\n * const validated = runArgsSchema.parse(args);\n * ```\n */\nexport const runArgsSchema = z.object({\n inputs: z.record(z.string(), z.any()),\n});\n\n/**\n * Type representing validated flow run arguments.\n *\n * This type is inferred from the runArgsSchema and ensures type safety\n * when passing inputs to flow execution.\n */\nexport type RunArgs = z.infer<typeof runArgsSchema>;\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=require(`./uploadista-error-DdTP-Rjx.cjs`),t=require(`./types-Dj9g8ocl.cjs`),n=require(`./upload-DvLp6TXO.cjs`);let r=require(`effect`);r=e.a(r);let i=require(`zod`);i=e.a(i);function a({source:e,target:t,sourcePort:n,targetPort:r}){return{source:e,target:t,sourcePort:n,targetPort:r}}let o=function(e){return e.JobStart=`job-start`,e.JobEnd=`job-end`,e.FlowStart=`flow-start`,e.FlowEnd=`flow-end`,e.FlowError=`flow-error`,e.NodeStart=`node-start`,e.NodeEnd=`node-end`,e.NodePause=`node-pause`,e.NodeResume=`node-resume`,e.NodeError=`node-error`,e.NodeStream=`node-stream`,e.NodeResponse=`node-response`,e}({}),s=function(e){return e.input=`input`,e.process=`process`,e.output=`output`,e.conditional=`conditional`,e.multiplex=`multiplex`,e.merge=`merge`,e}({});function c({id:t,name:n,description:i,type:a,inputSchema:o,outputSchema:s,run:c,condition:l,multiInput:u=!1,multiOutput:d=!1,pausable:f=!1,retry:p}){return r.Effect.succeed({id:t,name:n,description:i,type:a,inputSchema:o,outputSchema:s,pausable:f,run:({data:i,jobId:a,flowId:l,storageId:u,clientId:d})=>r.Effect.gen(function*(){let f=yield*c({data:yield*r.Effect.try({try:()=>o.parse(i),catch:r=>{let i=r instanceof Error?r.message:String(r);return e.n.fromCode(`FLOW_INPUT_VALIDATION_ERROR`,{body:`Node '${n}' (${t}) input validation failed: ${i}`,cause:r})}}),jobId:a,storageId:u,flowId:l,clientId:d});return f.type===`waiting`?f:{type:`complete`,data:yield*r.Effect.try({try:()=>s.parse(f.data),catch:r=>{let i=r instanceof Error?r.message:String(r);return e.n.fromCode(`FLOW_OUTPUT_VALIDATION_ERROR`,{body:`Node '${n}' (${t}) output validation failed: ${i}`,cause:r})}})}}),condition:l,multiInput:u,multiOutput:d,retry:p})}const l=e=>({id:e.id,name:e.name,description:e.description,type:e.type});var u=class{maxConcurrency;constructor(e={}){this.maxConcurrency=e.maxConcurrency??4}groupNodesByExecutionLevel(e,t){let n={},r={};e.forEach(e=>{n[e.id]=[],r[e.id]=0}),t.forEach(e=>{n[e.source]?.push(e.target),r[e.target]=(r[e.target]||0)+1});let i=[],a=new Set,o=0;for(;a.size<e.length;){let e=Object.keys(r).filter(e=>r[e]===0&&!a.has(e));if(e.length===0)throw Error(`Cycle detected in flow graph - cannot execute in parallel`);i.push({level:o++,nodes:e}),e.forEach(e=>{a.add(e),delete r[e],n[e]?.forEach(e=>{r[e]!==void 0&&r[e]--})})}return i}executeNodesInParallel(e){return r.Effect.all(e.map(e=>e()),{concurrency:this.maxConcurrency})}canExecuteInParallel(e,t,n){return e.every(e=>(n[e]||[]).every(e=>t.has(e)))}getStats(){return{maxConcurrency:this.maxConcurrency}}};const d=(e,t)=>{if(e===t)return!0;try{return!!(e&&t&&typeof e==`object`&&typeof t==`object`)}catch{return!0}};var f=class{typeChecker;constructor(e=d){this.typeChecker=e}validateConnection(e,t,n){return this.getCompatibleTypes(e.outputSchema,t.inputSchema)}getCompatibleTypes(e,t){return this.typeChecker(e,t)}validateFlow(e,t){let n=[],r=new Map(e.map(e=>[e.id,e]));for(let e of t){let t=r.get(e.source),i=r.get(e.target);if(!t){n.push(`Source node ${e.source} not found`);continue}if(!i){n.push(`Target node ${e.target} not found`);continue}this.validateConnection(t,i,e)||n.push(`Schema mismatch: ${t.id} output schema incompatible with ${i.id} input schema`)}return{isValid:n.length===0,errors:n}}getExpectedInputSchemas(e,t,n){let r=new Map(t.map(e=>[e.id,e])),i={};for(let t of n)if(t.target===e){let e=r.get(t.source);if(e){let n=t.sourcePort||t.source;i[n]=e.outputSchema}}return i}getActualOutputSchemas(e,t,n){let r=new Map(t.map(e=>[e.id,e])),i={};for(let t of n)if(t.source===e){let e=r.get(t.target);if(e){let n=t.targetPort||t.target;i[n]=e.inputSchema}}return i}validateData(e,t){try{return t.parse(e),{isValid:!0,errors:[]}}catch(e){return e instanceof Error&&`errors`in e?{isValid:!1,errors:e.errors.map(e=>`${e.path.join(`.`)}: ${e.message}`)}:{isValid:!1,errors:[e instanceof Error?e.message:`Validation failed`]}}}};const p=e=>({id:e.id,name:e.name,nodes:e.nodes.map(l),edges:e.edges});function m(t){return r.Effect.gen(function*(){let n=yield*r.Effect.all(t.nodes.map(e=>r.Effect.isEffect(e)?e:r.Effect.succeed(e))),{flowId:a,name:s,onEvent:c,edges:l,inputSchema:d,outputSchema:p,typeChecker:m}=t,h=n,g=new f(m),_=()=>{let e={},t={},n={};return h.forEach(r=>{e[r.id]=[],n[r.id]=[],t[r.id]=0}),l.forEach(r=>{e[r.source]?.push(r.target),n[r.target]?.push(r.source),t[r.target]=(t[r.target]||0)+1}),{graph:e,reverseGraph:n,inDegree:t}},v=()=>{let{graph:e,inDegree:t}=_(),n=[],r=[];for(Object.keys(t).forEach(e=>{t[e]===0&&n.push(e)});n.length>0;){let i=n.shift();if(!i)throw Error(`No current node found`);r.push(i),e[i]?.forEach(e=>{t[e]=(t[e]||0)-1,t[e]===0&&n.push(e)})}return r},y=(e,t)=>{if(!e.condition)return r.Effect.succeed(!0);let{field:n,operator:i,value:a}=e.condition,o=t,s=o?.metadata?.[n]||o?.[n],c=(()=>{switch(i){case`equals`:return s===a;case`notEquals`:return s!==a;case`greaterThan`:return Number(s)>Number(a);case`lessThan`:return Number(s)<Number(a);case`contains`:return String(s).includes(String(a));case`startsWith`:return String(s).startsWith(String(a));default:return!0}})();return r.Effect.succeed(c)},b=(e,t)=>{let{reverseGraph:n}=_(),r=n[e]||[],i={};return r.forEach(e=>{let n=t.get(e);n!==void 0&&(i[e]=n)}),i},x=e=>{let t=h.filter(e=>e.type===`input`),n={};return t.forEach(t=>{e&&typeof e==`object`&&t.id in e&&(n[t.id]=d.parse(e[t.id]))}),n},S=e=>{let t=h.filter(e=>e.type===`output`),n={};return t.forEach(t=>{let r=e.get(t.id);r!==void 0&&(n[t.id]=r)}),n},C=(t,n,i,s,l,u,d)=>r.Effect.gen(function*(){let f=l.get(t);if(!f)return yield*e.n.fromCode(`FLOW_NODE_NOT_FOUND`).toEffect();c&&(yield*c({jobId:u,flowId:a,nodeId:t,eventType:o.NodeStart,nodeName:f.name,nodeType:f.type}));let p=f.retry?.maxRetries??0,m=f.retry?.retryDelay??1e3,h=f.retry?.exponentialBackoff??!0,g=0,_=null;for(;g<=p;)try{let r,l={};if(f.type===`input`){if(r=i[t],r===void 0)return yield*e.n.fromCode(`FLOW_NODE_ERROR`,{cause:Error(`Input node ${t} has no input data`)}).toEffect()}else{if(l=b(t,s),Object.keys(l).length===0)return yield*e.n.fromCode(`FLOW_NODE_ERROR`,{cause:Error(`Node ${t} has no input data`)}).toEffect();if(f.multiInput)r=l;else{let n=Object.keys(l)[0];if(!n)return yield*e.n.fromCode(`FLOW_NODE_ERROR`,{cause:Error(`Node ${t} has no input data`)}).toEffect();r=l[n]}}if(f.type===`conditional`&&!(yield*y(f,r)))return c&&(yield*c({jobId:u,flowId:a,nodeId:t,eventType:o.NodeEnd,nodeName:f.name})),{nodeId:t,result:r,success:!0,waiting:!1};let p=yield*f.run({data:r,inputs:l,jobId:u,flowId:a,storageId:n,clientId:d});if(p.type===`waiting`){let e=p.partialData;return c&&(yield*c({jobId:u,flowId:a,nodeId:t,eventType:o.NodePause,nodeName:f.name,partialData:e})),{nodeId:t,result:e,success:!0,waiting:!0}}let m=p.data;return c&&(yield*c({jobId:u,flowId:a,nodeId:t,eventType:o.NodeEnd,nodeName:f.name,result:m})),{nodeId:t,result:m,success:!0,waiting:!1}}catch(n){if(_=n instanceof e.n?n:e.n.fromCode(`FLOW_NODE_ERROR`,{cause:n}),g<p){g++;let e=h?m*2**(g-1):m;yield*r.Effect.logWarning(`Node ${t} (${f.name}) failed, retrying (${g}/${p}) after ${e}ms`),yield*r.Effect.sleep(e);continue}return c&&(yield*c({jobId:u,flowId:a,nodeId:t,eventType:o.NodeError,nodeName:f.name,error:_.body,retryCount:g})),yield*_.toEffect()}return _?yield*_.toEffect():yield*e.n.fromCode(`FLOW_NODE_ERROR`,{cause:Error(`Unexpected error in retry loop`)}).toEffect()}),w=({inputs:n,storageId:s,jobId:d,resumeFrom:f,clientId:m})=>r.Effect.gen(function*(){!f&&c&&(yield*c({jobId:d,eventType:o.FlowStart,flowId:a}));let g=x(n||{}),_,y,b;if(f)_=f.executionOrder,y=f.nodeResults,b=f.currentIndex;else if(_=v(),y=new Map,b=0,_.length!==h.length)return yield*e.n.fromCode(`FLOW_CYCLE_ERROR`).toEffect();let w=new Map(h.map(e=>[e.id,e]));if(t.parallelExecution?.enabled??!1){yield*r.Effect.logDebug(`Flow ${a}: Executing in parallel mode (maxConcurrency: ${t.parallelExecution?.maxConcurrency??4})`);let e=new u({maxConcurrency:t.parallelExecution?.maxConcurrency??4}),n=e.groupNodesByExecutionLevel(h,l);yield*r.Effect.logDebug(`Flow ${a}: Grouped nodes into ${n.length} execution levels`);let i={};h.forEach(e=>{i[e.id]=[]}),l.forEach(e=>{i[e.target]?.push(e.source)});for(let t of n){yield*r.Effect.logDebug(`Flow ${a}: Executing level ${t.level} with nodes: ${t.nodes.join(`, `)}`);let n=t.nodes.map(e=>()=>r.Effect.gen(function*(){if(f&&e===f.executionOrder[b]&&c){let t=w.get(e);t&&(yield*c({jobId:d,flowId:a,nodeId:e,eventType:o.NodeResume,nodeName:t.name,nodeType:t.type}))}return{nodeId:e,nodeResult:yield*C(e,s,g,y,w,d,m)}})),i=yield*e.executeNodesInParallel(n);for(let{nodeId:e,nodeResult:t}of i){if(t.waiting)return t.result!==void 0&&y.set(e,t.result),{type:`paused`,nodeId:e,executionState:{executionOrder:_,currentIndex:_.indexOf(e),inputs:g}};t.success&&y.set(e,t.result)}}}else{yield*r.Effect.logDebug(`Flow ${a}: Executing in sequential mode`);for(let t=b;t<_.length;t++){let n=_[t];if(!n)return yield*e.n.fromCode(`FLOW_NODE_NOT_FOUND`).toEffect();if(f&&t===b&&c){let e=w.get(n);e&&(yield*c({jobId:d,flowId:a,nodeId:n,eventType:o.NodeResume,nodeName:e.name,nodeType:e.type}))}let r=yield*C(n,s,g,y,w,d,m);if(r.waiting)return r.result!==void 0&&y.set(r.nodeId,r.result),{type:`paused`,nodeId:r.nodeId,executionState:{executionOrder:_,currentIndex:t,inputs:g}};r.success&&y.set(r.nodeId,r.result)}}let T=S(y),E=i.z.record(i.z.string(),p).safeParse(T);if(!E.success){let t=`Flow output validation failed: ${E.error.message}. Expected outputs: ${JSON.stringify(Object.keys(S(y)))}. Output nodes: ${h.filter(e=>e.type===`output`).map(e=>e.id).join(`, `)}`;return c&&(yield*c({jobId:d,eventType:o.FlowError,flowId:a,error:t})),yield*e.n.fromCode(`FLOW_OUTPUT_VALIDATION_ERROR`,{body:t,cause:E.error}).toEffect()}let D=E.data;return c&&(yield*c({jobId:d,eventType:o.FlowEnd,flowId:a,result:D})),{type:`completed`,result:D}});return{id:a,name:s,nodes:h,edges:l,inputSchema:d,outputSchema:p,onEvent:c,run:({inputs:e,storageId:t,jobId:n,clientId:r})=>w({inputs:e,storageId:t,jobId:n,clientId:r}),resume:({jobId:e,storageId:t,nodeResults:n,executionState:r,clientId:i})=>w({inputs:r.inputs,storageId:t,jobId:e,resumeFrom:{executionOrder:r.executionOrder,nodeResults:new Map(Object.entries(n)),currentIndex:r.currentIndex},clientId:i}),validateTypes:()=>{let e=h;return g.validateFlow(e,l)},validateInputs:e=>g.validateData(e,d),validateOutputs:e=>g.validateData(e,p)}})}var h=class extends r.Context.Tag(`FlowProvider`)(){},g=class extends r.Context.Tag(`FlowServer`)(){};const _=e=>typeof e==`object`&&!!e&&`id`in e;function v(e,t,n){let i=i=>{let a=e=>r.Effect.gen(function*(){let t=yield*n.get(i);t&&(yield*n.set(i,{...t,...e,updatedAt:new Date}))});return s=>r.Effect.gen(function*(){switch(e.onEvent&&(yield*r.Effect.catchAll(e.onEvent(s),e=>(r.Effect.logError(`Original onEvent failed`,e),r.Effect.succeed({eventId:null})))),yield*t.emit(i,s),r.Effect.logInfo(`Updating job ${i} with event ${s.eventType}`),s.eventType){case o.FlowStart:yield*a({status:`running`});break;case o.FlowEnd:break;case o.FlowError:yield*a({status:`failed`,error:s.error});break;case o.NodeStart:yield*r.Effect.gen(function*(){let e=yield*n.get(i);if(e){let t=e.tasks.find(e=>e.nodeId===s.nodeId)?e.tasks.map(e=>e.nodeId===s.nodeId?{...e,status:`running`,updatedAt:new Date}:e):[...e.tasks,{nodeId:s.nodeId,status:`running`,createdAt:new Date,updatedAt:new Date}];yield*n.set(i,{...e,tasks:t,updatedAt:new Date})}});break;case o.NodePause:yield*r.Effect.gen(function*(){let e=yield*n.get(i);if(e){let t=e.tasks.find(e=>e.nodeId===s.nodeId)?e.tasks.map(e=>e.nodeId===s.nodeId?{...e,status:`paused`,result:s.partialData,updatedAt:new Date}:e):[...e.tasks,{nodeId:s.nodeId,status:`paused`,result:s.partialData,createdAt:new Date,updatedAt:new Date}];yield*n.set(i,{...e,tasks:t,updatedAt:new Date})}});break;case o.NodeResume:yield*r.Effect.gen(function*(){let e=yield*n.get(i);if(e){let t=e.tasks.map(e=>e.nodeId===s.nodeId?{...e,status:`running`,updatedAt:new Date}:e);yield*n.set(i,{...e,tasks:t,updatedAt:new Date})}});break;case o.NodeEnd:yield*r.Effect.gen(function*(){let t=yield*n.get(i);if(t){let r=t.tasks.map(e=>e.nodeId===s.nodeId?{...e,status:`completed`,result:s.result,updatedAt:new Date}:e),a=e.nodes.find(e=>e.id===s.nodeId)?.type===`output`,o=s.result,c=t.intermediateFiles||[];a&&_(o)&&o.id?c=c.filter(e=>e!==o.id):!a&&_(o)&&o.id&&(c.includes(o.id)||c.push(o.id)),yield*n.set(i,{...t,tasks:r,intermediateFiles:c,updatedAt:new Date})}});break;case o.NodeError:yield*r.Effect.gen(function*(){let e=yield*n.get(i);if(e){let t=e.tasks.map(e=>e.nodeId===s.nodeId?{...e,status:`failed`,error:s.error,retryCount:s.retryCount,updatedAt:new Date}:e);yield*n.set(i,{...e,tasks:t,error:s.error,updatedAt:new Date})}});break}return{eventId:i}})};return{...e,run:t=>r.Effect.gen(function*(){let n=t.jobId||crypto.randomUUID(),r=i(n);return yield*(yield*m({flowId:e.id,name:e.name,nodes:e.nodes,edges:e.edges,inputSchema:e.inputSchema,outputSchema:e.outputSchema,onEvent:r})).run({...t,jobId:n,clientId:t.clientId})}),resume:t=>r.Effect.gen(function*(){let n=t.jobId,r=i(n);return yield*(yield*m({flowId:e.id,name:e.name,nodes:e.nodes,edges:e.edges,inputSchema:e.inputSchema,outputSchema:e.outputSchema,onEvent:r})).resume(t)})}}function y(){return r.Effect.gen(function*(){let i=yield*h,a=yield*t.g,s=yield*t.c,c=yield*n.n,l=(t,n)=>r.Effect.gen(function*(){let i=yield*s.get(t);return i?yield*s.set(t,{...i,...n}):yield*r.Effect.fail(e.n.fromCode(`FLOW_JOB_NOT_FOUND`,{cause:`Job ${t} not found`}))}),u=(e,t)=>r.Effect.gen(function*(){let n=yield*s.get(e);!n||!n.intermediateFiles||n.intermediateFiles.length===0||(yield*r.Effect.logInfo(`Cleaning up ${n.intermediateFiles.length} intermediate files for job ${e}`),yield*r.Effect.all(n.intermediateFiles.map(e=>r.Effect.gen(function*(){yield*c.delete(e,t),yield*r.Effect.logDebug(`Deleted intermediate file ${e}`)}).pipe(r.Effect.catchAll(t=>r.Effect.gen(function*(){return yield*r.Effect.logWarning(`Failed to delete intermediate file ${e}: ${t}`),r.Effect.succeed(void 0)})))),{concurrency:5}),yield*l(e,{intermediateFiles:[]}))}),d=({jobId:t,flow:n,storageId:i,clientId:c,inputs:d})=>r.Effect.gen(function*(){yield*l(t,{status:`running`});let e=yield*v(n,a,s).run({inputs:d,storageId:i,jobId:t,clientId:c});return e.type===`paused`?yield*l(t,{status:`paused`,pausedAt:e.nodeId,executionState:e.executionState,updatedAt:new Date}):(yield*l(t,{status:`completed`,result:e.result,updatedAt:new Date,endedAt:new Date}),yield*u(t,c)),e}).pipe(r.Effect.catchAll(n=>r.Effect.gen(function*(){yield*r.Effect.logError(`Flow execution failed`,n);let i=n instanceof e.n?n.body:String(n);yield*r.Effect.logInfo(`Updating job ${t} to failed status with error: ${i}`),yield*l(t,{status:`failed`,error:i,updatedAt:new Date}).pipe(r.Effect.catchAll(e=>r.Effect.gen(function*(){return yield*r.Effect.logError(`Failed to update job ${t}`,e),r.Effect.succeed(void 0)})));let d=yield*s.get(t);return d&&(yield*a.emit(t,{jobId:t,eventType:o.FlowError,flowId:d.flowId,error:i}).pipe(r.Effect.catchAll(e=>r.Effect.gen(function*(){return yield*r.Effect.logError(`Failed to emit FlowError event for job ${t}`,e),r.Effect.succeed(void 0)})))),yield*u(t,c).pipe(r.Effect.catchAll(e=>r.Effect.gen(function*(){return yield*r.Effect.logWarning(`Failed to cleanup intermediate files for job ${t}`,e),r.Effect.succeed(void 0)}))),r.Effect.fail(n)})));return{getFlow:(e,t)=>r.Effect.gen(function*(){return yield*i.getFlow(e,t)}),getFlowData:(e,t)=>r.Effect.gen(function*(){return p(yield*i.getFlow(e,t))}),runFlow:({flowId:t,storageId:n,clientId:a,inputs:o})=>r.Effect.gen(function*(){let c=yield*r.Effect.try({try:()=>W.parse({inputs:o}),catch:t=>e.n.fromCode(`FLOW_INPUT_VALIDATION_ERROR`,{cause:t})}),l=crypto.randomUUID(),u=new Date,f={id:l,flowId:t,storageId:n,clientId:a,status:`started`,createdAt:u,updatedAt:u,tasks:[]};yield*s.set(l,f);let p=yield*i.getFlow(t,a);return yield*r.Effect.forkDaemon(d({jobId:l,flow:p,storageId:n,clientId:a,inputs:c.inputs}).pipe(r.Effect.tapErrorCause(e=>r.Effect.logError(`Flow execution failed`,e)))),f}),getJobStatus:t=>r.Effect.gen(function*(){return(yield*s.get(t))||(yield*r.Effect.fail(e.n.fromCode(`FLOW_JOB_NOT_FOUND`,{cause:`Job ${t} not found`})))}),continueFlow:({jobId:t,nodeId:n,newData:c,clientId:d})=>r.Effect.gen(function*(){console.log(`continueFlow`,t,n,c);let f=yield*s.get(t);if(!f)return console.error(`Job not found`),yield*r.Effect.fail(e.n.fromCode(`FLOW_JOB_NOT_FOUND`,{cause:`Job ${t} not found`}));if(f.status!==`paused`)return console.error(`Job is not paused`),yield*r.Effect.fail(e.n.fromCode(`FLOW_JOB_ERROR`,{cause:`Job ${t} is not paused (status: ${f.status})`}));if(f.pausedAt!==n)return console.error(`Job is not paused at the expected node`),yield*r.Effect.fail(e.n.fromCode(`FLOW_JOB_ERROR`,{cause:`Job ${t} is paused at node ${f.pausedAt}, not ${n}`}));if(!f.executionState)return console.error(`Job has no execution state`),yield*r.Effect.fail(e.n.fromCode(`FLOW_JOB_ERROR`,{cause:`Job ${t} has no execution state`}));let p={...f.tasks.reduce((e,t)=>(t.result!==void 0&&(e[t.nodeId]=t.result),e),{}),[n]:c},m={...f.executionState.inputs,[n]:c};yield*l(t,{status:`running`});let h=yield*i.getFlow(f.flowId,f.clientId),g=r.Effect.gen(function*(){let n=v(h,a,s);if(!f.executionState)return yield*r.Effect.fail(e.n.fromCode(`FLOW_JOB_ERROR`,{cause:`Job ${t} has no execution state`}));let i=yield*n.resume({jobId:t,storageId:f.storageId,nodeResults:p,executionState:{...f.executionState,inputs:m},clientId:f.clientId});return i.type===`paused`?yield*l(t,{status:`paused`,pausedAt:i.nodeId,executionState:i.executionState,updatedAt:new Date}):(yield*l(t,{status:`completed`,pausedAt:void 0,executionState:void 0,result:i.result,updatedAt:new Date,endedAt:new Date}),yield*u(t,d)),i}).pipe(r.Effect.catchAll(n=>r.Effect.gen(function*(){yield*r.Effect.logError(`Flow resume failed`,n);let i=n instanceof e.n?n.body:String(n);yield*r.Effect.logInfo(`Updating job ${t} to failed status with error: ${i}`),yield*l(t,{status:`failed`,error:i,updatedAt:new Date}).pipe(r.Effect.catchAll(e=>r.Effect.gen(function*(){return yield*r.Effect.logError(`Failed to update job ${t}`,e),r.Effect.succeed(void 0)})));let c=yield*s.get(t);return c&&(yield*a.emit(t,{jobId:t,eventType:o.FlowError,flowId:c.flowId,error:i}).pipe(r.Effect.catchAll(e=>r.Effect.gen(function*(){return yield*r.Effect.logError(`Failed to emit FlowError event for job ${t}`,e),r.Effect.succeed(void 0)})))),yield*u(t,d).pipe(r.Effect.catchAll(e=>r.Effect.gen(function*(){return yield*r.Effect.logWarning(`Failed to cleanup intermediate files for job ${t}`,e),r.Effect.succeed(void 0)}))),r.Effect.fail(n)})));return yield*r.Effect.forkDaemon(g.pipe(r.Effect.tapErrorCause(e=>r.Effect.logError(`Flow resume failed`,e)))),(yield*s.get(t))||(yield*r.Effect.fail(e.n.fromCode(`FLOW_JOB_NOT_FOUND`,{cause:`Job ${t} not found after update`})))}),subscribeToFlowEvents:(e,t)=>r.Effect.gen(function*(){yield*a.subscribe(e,t)}),unsubscribeFromFlowEvents:e=>r.Effect.gen(function*(){yield*a.unsubscribe(e)})}})}const b=r.Layer.effect(g,y()),x=e=>({type:`complete`,data:e}),S=e=>({type:`waiting`,partialData:e});function C(e){if(!e)return{type:``,fileName:``,metadata:void 0,metadataJson:void 0};let t={...e},n=String(t.type||t.mimeType||t[`content-type`]||``);n&&(t.type||=n,t.mimeType||=n);let r=String(t.fileName||t.originalName||t.name||``);return r&&(t.fileName||=r,t.originalName||=r,t.name||=r),{type:n,fileName:r,metadata:t,metadataJson:JSON.stringify(t)}}const w=i.z.object({operation:i.z.literal(`init`),storageId:i.z.string(),metadata:i.z.record(i.z.string(),i.z.any()).optional()}),T=i.z.object({operation:i.z.literal(`finalize`),uploadId:i.z.string()}),E=i.z.object({operation:i.z.literal(`url`),url:i.z.string(),storageId:i.z.string().optional(),metadata:i.z.record(i.z.string(),i.z.any()).optional()}),D=i.z.union([w,T,E]),O=i.z.object({allowedMimeTypes:i.z.array(i.z.string()).optional(),minSize:i.z.number().positive().optional(),maxSize:i.z.number().positive().optional()});function k(t,n){return r.Effect.gen(function*(){if(n){if(n.allowedMimeTypes&&n.allowedMimeTypes.length>0&&!n.allowedMimeTypes.some(e=>{if(e.endsWith(`/*`)){let n=e.slice(0,-2);return t.type.startsWith(n)}return t.type===e}))throw yield*e.n.fromCode(`VALIDATION_ERROR`,{cause:Error(`File type "${t.type}" is not allowed. Allowed types: ${n.allowedMimeTypes.join(`, `)}`)}).toEffect();if(n.minSize!==void 0&&t.size<n.minSize)throw yield*e.n.fromCode(`VALIDATION_ERROR`,{cause:Error(`File size (${t.size} bytes) is below minimum (${n.minSize} bytes)`)}).toEffect();if(n.maxSize!==void 0&&t.size>n.maxSize)throw yield*e.n.fromCode(`VALIDATION_ERROR`,{cause:Error(`File size (${t.size} bytes) exceeds maximum (${n.maxSize} bytes)`)}).toEffect()}})}function A(i,a){return r.Effect.gen(function*(){let o=yield*n.n;return yield*c({id:i,name:`Input`,description:`Handles file input through multiple methods - streaming upload (init/finalize) or direct URL fetch`,type:s.input,inputSchema:D,outputSchema:t.i,run:({data:t,flowId:s,jobId:c,clientId:l})=>r.Effect.gen(function*(){switch(t.operation){case`init`:{let e={storageId:t.storageId,size:t.metadata?.size||0,type:t.metadata?.mimeType||`application/octet-stream`,fileName:t.metadata?.originalName,lastModified:t.metadata?.size?Date.now():void 0,metadata:t.metadata?JSON.stringify(t.metadata):void 0,flow:{flowId:s,nodeId:i,jobId:c}};return S(yield*o.createUpload(e,l))}case`finalize`:{let e=yield*o.getUpload(t.uploadId),{type:n}=C(e.metadata);return yield*k({type:n,size:e.size||0},a),x(e)}case`url`:{let e=yield*n.o(t.url),r=yield*n.a(e),u=t.metadata?.mimeType||e.headers.get(`content-type`)||`application/octet-stream`,d=t.metadata?.size||Number(e.headers.get(`content-length`)||0),f=t.metadata?.originalName||t.url.split(`/`).pop()||`file`;yield*k({type:u,size:d},a);let p=new ReadableStream({start(e){e.enqueue(new Uint8Array(r)),e.close()}}),m={storageId:t.storageId||`buffer`,size:d,type:u,fileName:f,lastModified:Date.now(),metadata:t.metadata?JSON.stringify(t.metadata):void 0};return x({...yield*o.upload(m,l,p),flow:{flowId:s,nodeId:i,jobId:c}})}default:throw yield*e.n.fromCode(`VALIDATION_ERROR`,{cause:Error(`Invalid operation`)}).toEffect()}})})})}const j=i.z.object({});function M(i,a=e=>r.Effect.succeed(e)){return r.Effect.gen(function*(){let o=yield*n.n;return yield*c({id:i,name:`Storage`,description:`Stores a file in the storage`,type:s.output,inputSchema:t.i,outputSchema:t.i,run:({data:t,storageId:n,flowId:s,jobId:c,clientId:l})=>r.Effect.gen(function*(){let{type:u,fileName:d,metadata:f,metadataJson:p}=C(t.metadata),m={flowId:s,nodeId:i,jobId:c},h=f?{...t,metadata:f}:t,g=yield*o.getUpload(t.id);if(!g.id)return yield*r.Effect.fail(e.n.fromCode(`FILE_READ_ERROR`,Error(`Upload Key is undefined`)));if(g.storage.id===n)return x(yield*a({...h,flow:m}));let _=yield*o.read(t.id,l),v=new ReadableStream({start(e){e.enqueue(_),e.close()}}),y=yield*o.upload({storageId:n,size:_.byteLength,type:u,fileName:d,lastModified:0,metadata:p,flow:m},l,v),b=C(y.metadata);return x(yield*a(b.metadata?{...y,metadata:b.metadata}:y))})})})}function N({id:e,name:i,description:a,transform:o}){return r.Effect.gen(function*(){let l=yield*n.n;return yield*c({id:e,name:i,description:a,type:s.process,inputSchema:t.i,outputSchema:t.i,run:({data:t,storageId:n,flowId:i,jobId:a,clientId:s})=>r.Effect.gen(function*(){let r={flowId:i,nodeId:e,jobId:a},c=yield*o(yield*l.read(t.id,s),t),u=c instanceof Uint8Array?c:c.bytes,d=c instanceof Uint8Array?void 0:c.type,f=c instanceof Uint8Array?void 0:c.fileName,p=new ReadableStream({start(e){e.enqueue(u),e.close()}}),{type:m,fileName:h,metadata:g,metadataJson:_}=C(t.metadata),v=yield*l.upload({storageId:n,size:u.byteLength,type:d??m,fileName:f??h,lastModified:0,metadata:_,flow:r},s,p);return x(g?{...v,metadata:g}:v)})})})}var P=class extends r.Context.Tag(`CredentialProvider`)(){},F=class extends r.Context.Tag(`ImageAiPlugin`)(){},I=class extends r.Context.Tag(`ImagePlugin`)(){};const L=i.z.object({serviceType:i.z.enum([`replicate`]).optional()}),R=i.z.object({quality:i.z.number().min(0).max(100),format:i.z.enum([`jpeg`,`webp`,`png`,`avif`])}),z=i.z.object({serviceType:i.z.enum([`replicate`]).optional()}),B=i.z.object({width:i.z.number().positive().optional(),height:i.z.number().positive().optional(),fit:i.z.enum([`contain`,`cover`,`fill`])}).refine(e=>e.width||e.height,`Either width or height must be specified for resize`);var V=class extends r.Context.Tag(`ZipPlugin`)(){};const H=(e,t)=>{if(e.length===0)return t;let[n,...r]=e;return r.reduce((e,t)=>i.z.union([e,t]),n)};function U(t){return r.Effect.gen(function*(){let n=Object.entries(t.nodes),a=e=>r.Effect.isEffect(e)?e:r.Effect.succeed(e),o=yield*r.Effect.forEach(n,([t,n])=>r.Effect.flatMap(a(n),n=>n.id===t?r.Effect.succeed([t,n]):r.Effect.fail(e.n.fromCode(`FLOW_NODE_ERROR`,{cause:Error(`Node key ${t} does not match node id ${n.id}`)})))),c=Object.fromEntries(o),l=o.map(([,e])=>e),u=o.filter(([,e])=>e.type===s.input).map(([,e])=>e.inputSchema),d=o.filter(([,e])=>e.type===s.output).map(([,e])=>e.outputSchema),f=t.inputSchema??H(u,i.z.unknown()),p=t.outputSchema??H(d,i.z.unknown()),h=t.edges.map(e=>({source:c[e.source]?.id??e.source,target:c[e.target]?.id??e.target,sourcePort:e.sourcePort,targetPort:e.targetPort}));return yield*m({flowId:t.flowId,name:t.name,nodes:l,edges:h,inputSchema:f,outputSchema:p,typeChecker:t.typeChecker,onEvent:t.onEvent,parallelExecution:t.parallelExecution})})}const W=i.z.object({inputs:i.z.record(i.z.string(),i.z.any())});Object.defineProperty(exports,`A`,{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,`C`,{enumerable:!0,get:function(){return b}}),Object.defineProperty(exports,`D`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`E`,{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,`O`,{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,`S`,{enumerable:!0,get:function(){return y}}),Object.defineProperty(exports,`T`,{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,`_`,{enumerable:!0,get:function(){return C}}),Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return z}}),Object.defineProperty(exports,`b`,{enumerable:!0,get:function(){return h}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return I}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return N}}),Object.defineProperty(exports,`f`,{enumerable:!0,get:function(){return M}}),Object.defineProperty(exports,`g`,{enumerable:!0,get:function(){return O}}),Object.defineProperty(exports,`h`,{enumerable:!0,get:function(){return D}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return B}}),Object.defineProperty(exports,`j`,{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,`k`,{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return F}}),Object.defineProperty(exports,`m`,{enumerable:!0,get:function(){return A}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return U}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return R}}),Object.defineProperty(exports,`p`,{enumerable:!0,get:function(){return j}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return V}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return L}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return W}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return P}}),Object.defineProperty(exports,`v`,{enumerable:!0,get:function(){return x}}),Object.defineProperty(exports,`w`,{enumerable:!0,get:function(){return m}}),Object.defineProperty(exports,`x`,{enumerable:!0,get:function(){return g}}),Object.defineProperty(exports,`y`,{enumerable:!0,get:function(){return S}});
|