@uploadista/core 0.0.2
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 +5 -0
- package/.turbo/turbo-check.log +231 -0
- package/.turbo/turbo-format.log +5 -0
- package/LICENSE +21 -0
- package/README.md +1120 -0
- package/dist/chunk-CUT6urMc.cjs +1 -0
- package/dist/debounce-C2SeqcxD.js +2 -0
- package/dist/debounce-C2SeqcxD.js.map +1 -0
- package/dist/debounce-LZK7yS7Z.cjs +1 -0
- package/dist/errors/index.cjs +1 -0
- package/dist/errors/index.d.cts +3 -0
- package/dist/errors/index.d.ts +3 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +2 -0
- package/dist/errors/uploadista-error.d.ts +209 -0
- package/dist/errors/uploadista-error.d.ts.map +1 -0
- package/dist/errors/uploadista-error.js +322 -0
- package/dist/errors-8i_aMxOE.js +1 -0
- package/dist/errors-CRm1FHHT.cjs +0 -0
- package/dist/flow/edge.d.ts +47 -0
- package/dist/flow/edge.d.ts.map +1 -0
- package/dist/flow/edge.js +40 -0
- package/dist/flow/event.d.ts +206 -0
- package/dist/flow/event.d.ts.map +1 -0
- package/dist/flow/event.js +53 -0
- package/dist/flow/flow-server.d.ts +223 -0
- package/dist/flow/flow-server.d.ts.map +1 -0
- package/dist/flow/flow-server.js +614 -0
- package/dist/flow/flow.d.ts +238 -0
- package/dist/flow/flow.d.ts.map +1 -0
- package/dist/flow/flow.js +629 -0
- package/dist/flow/index.cjs +1 -0
- package/dist/flow/index.d.cts +6 -0
- package/dist/flow/index.d.ts +24 -0
- package/dist/flow/index.d.ts.map +1 -0
- package/dist/flow/index.js +24 -0
- package/dist/flow/node.d.ts +136 -0
- package/dist/flow/node.d.ts.map +1 -0
- package/dist/flow/node.js +153 -0
- package/dist/flow/nodes/index.d.ts +8 -0
- package/dist/flow/nodes/index.d.ts.map +1 -0
- package/dist/flow/nodes/index.js +7 -0
- package/dist/flow/nodes/input-node.d.ts +78 -0
- package/dist/flow/nodes/input-node.d.ts.map +1 -0
- package/dist/flow/nodes/input-node.js +233 -0
- package/dist/flow/nodes/storage-node.d.ts +67 -0
- package/dist/flow/nodes/storage-node.d.ts.map +1 -0
- package/dist/flow/nodes/storage-node.js +94 -0
- package/dist/flow/nodes/streaming-input-node.d.ts +69 -0
- package/dist/flow/nodes/streaming-input-node.d.ts.map +1 -0
- package/dist/flow/nodes/streaming-input-node.js +156 -0
- package/dist/flow/nodes/transform-node.d.ts +85 -0
- package/dist/flow/nodes/transform-node.d.ts.map +1 -0
- package/dist/flow/nodes/transform-node.js +107 -0
- package/dist/flow/parallel-scheduler.d.ts +175 -0
- package/dist/flow/parallel-scheduler.d.ts.map +1 -0
- package/dist/flow/parallel-scheduler.js +193 -0
- package/dist/flow/plugins/credential-provider.d.ts +47 -0
- package/dist/flow/plugins/credential-provider.d.ts.map +1 -0
- package/dist/flow/plugins/credential-provider.js +24 -0
- package/dist/flow/plugins/image-ai-plugin.d.ts +61 -0
- package/dist/flow/plugins/image-ai-plugin.d.ts.map +1 -0
- package/dist/flow/plugins/image-ai-plugin.js +21 -0
- package/dist/flow/plugins/image-plugin.d.ts +52 -0
- package/dist/flow/plugins/image-plugin.d.ts.map +1 -0
- package/dist/flow/plugins/image-plugin.js +22 -0
- package/dist/flow/plugins/types/describe-image-node.d.ts +16 -0
- package/dist/flow/plugins/types/describe-image-node.d.ts.map +1 -0
- package/dist/flow/plugins/types/describe-image-node.js +9 -0
- package/dist/flow/plugins/types/index.d.ts +9 -0
- package/dist/flow/plugins/types/index.d.ts.map +1 -0
- package/dist/flow/plugins/types/index.js +8 -0
- package/dist/flow/plugins/types/optimize-node.d.ts +20 -0
- package/dist/flow/plugins/types/optimize-node.d.ts.map +1 -0
- package/dist/flow/plugins/types/optimize-node.js +11 -0
- package/dist/flow/plugins/types/remove-background-node.d.ts +16 -0
- package/dist/flow/plugins/types/remove-background-node.d.ts.map +1 -0
- package/dist/flow/plugins/types/remove-background-node.js +9 -0
- package/dist/flow/plugins/types/resize-node.d.ts +21 -0
- package/dist/flow/plugins/types/resize-node.d.ts.map +1 -0
- package/dist/flow/plugins/types/resize-node.js +16 -0
- package/dist/flow/plugins/zip-plugin.d.ts +62 -0
- package/dist/flow/plugins/zip-plugin.d.ts.map +1 -0
- package/dist/flow/plugins/zip-plugin.js +21 -0
- package/dist/flow/typed-flow.d.ts +90 -0
- package/dist/flow/typed-flow.d.ts.map +1 -0
- package/dist/flow/typed-flow.js +59 -0
- package/dist/flow/types/flow-file.d.ts +45 -0
- package/dist/flow/types/flow-file.d.ts.map +1 -0
- package/dist/flow/types/flow-file.js +27 -0
- package/dist/flow/types/flow-job.d.ts +118 -0
- package/dist/flow/types/flow-job.d.ts.map +1 -0
- package/dist/flow/types/flow-job.js +11 -0
- package/dist/flow/types/flow-types.d.ts +321 -0
- package/dist/flow/types/flow-types.d.ts.map +1 -0
- package/dist/flow/types/flow-types.js +52 -0
- package/dist/flow/types/index.d.ts +4 -0
- package/dist/flow/types/index.d.ts.map +1 -0
- package/dist/flow/types/index.js +3 -0
- package/dist/flow/types/run-args.d.ts +38 -0
- package/dist/flow/types/run-args.d.ts.map +1 -0
- package/dist/flow/types/run-args.js +30 -0
- package/dist/flow/types/type-validator.d.ts +26 -0
- package/dist/flow/types/type-validator.d.ts.map +1 -0
- package/dist/flow/types/type-validator.js +134 -0
- package/dist/flow/utils/resolve-upload-metadata.d.ts +11 -0
- package/dist/flow/utils/resolve-upload-metadata.d.ts.map +1 -0
- package/dist/flow/utils/resolve-upload-metadata.js +28 -0
- package/dist/flow-2zXnEiWL.cjs +1 -0
- package/dist/flow-CRaKy7Vj.js +2 -0
- package/dist/flow-CRaKy7Vj.js.map +1 -0
- package/dist/generate-id-Dm-Vboxq.d.ts +34 -0
- package/dist/generate-id-Dm-Vboxq.d.ts.map +1 -0
- package/dist/generate-id-LjJRLD6N.d.cts +34 -0
- package/dist/generate-id-LjJRLD6N.d.cts.map +1 -0
- package/dist/generate-id-xHp_Z7Cl.cjs +1 -0
- package/dist/generate-id-yohS1ZDk.js +2 -0
- package/dist/generate-id-yohS1ZDk.js.map +1 -0
- package/dist/index-BO8GZlbD.d.cts +1040 -0
- package/dist/index-BO8GZlbD.d.cts.map +1 -0
- package/dist/index-BoGG5KAY.d.ts +1 -0
- package/dist/index-BtBZHVmz.d.cts +1 -0
- package/dist/index-D-CoVpkZ.d.ts +1004 -0
- package/dist/index-D-CoVpkZ.d.ts.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/logger/logger.cjs +1 -0
- package/dist/logger/logger.d.cts +8 -0
- package/dist/logger/logger.d.cts.map +1 -0
- package/dist/logger/logger.d.ts +5 -0
- package/dist/logger/logger.d.ts.map +1 -0
- package/dist/logger/logger.js +10 -0
- package/dist/logger/logger.js.map +1 -0
- package/dist/semaphore-0ZwjVpyF.js +2 -0
- package/dist/semaphore-0ZwjVpyF.js.map +1 -0
- package/dist/semaphore-BHprIjFI.d.cts +37 -0
- package/dist/semaphore-BHprIjFI.d.cts.map +1 -0
- package/dist/semaphore-DThupBkc.d.ts +37 -0
- package/dist/semaphore-DThupBkc.d.ts.map +1 -0
- package/dist/semaphore-DVrONiAV.cjs +1 -0
- package/dist/stream-limiter-CoWKv39w.js +2 -0
- package/dist/stream-limiter-CoWKv39w.js.map +1 -0
- package/dist/stream-limiter-JgOwmkMa.cjs +1 -0
- package/dist/streams/multi-stream.cjs +1 -0
- package/dist/streams/multi-stream.d.cts +91 -0
- package/dist/streams/multi-stream.d.cts.map +1 -0
- package/dist/streams/multi-stream.d.ts +86 -0
- package/dist/streams/multi-stream.d.ts.map +1 -0
- package/dist/streams/multi-stream.js +149 -0
- package/dist/streams/multi-stream.js.map +1 -0
- package/dist/streams/stream-limiter.cjs +1 -0
- package/dist/streams/stream-limiter.d.cts +36 -0
- package/dist/streams/stream-limiter.d.cts.map +1 -0
- package/dist/streams/stream-limiter.d.ts +27 -0
- package/dist/streams/stream-limiter.d.ts.map +1 -0
- package/dist/streams/stream-limiter.js +49 -0
- package/dist/streams/stream-splitter.cjs +1 -0
- package/dist/streams/stream-splitter.d.cts +68 -0
- package/dist/streams/stream-splitter.d.cts.map +1 -0
- package/dist/streams/stream-splitter.d.ts +51 -0
- package/dist/streams/stream-splitter.d.ts.map +1 -0
- package/dist/streams/stream-splitter.js +175 -0
- package/dist/streams/stream-splitter.js.map +1 -0
- package/dist/types/data-store-registry.d.ts +13 -0
- package/dist/types/data-store-registry.d.ts.map +1 -0
- package/dist/types/data-store-registry.js +4 -0
- package/dist/types/data-store.d.ts +316 -0
- package/dist/types/data-store.d.ts.map +1 -0
- package/dist/types/data-store.js +157 -0
- package/dist/types/event-broadcaster.d.ts +28 -0
- package/dist/types/event-broadcaster.d.ts.map +1 -0
- package/dist/types/event-broadcaster.js +6 -0
- package/dist/types/event-emitter.d.ts +378 -0
- package/dist/types/event-emitter.d.ts.map +1 -0
- package/dist/types/event-emitter.js +223 -0
- package/dist/types/index.cjs +1 -0
- package/dist/types/index.d.cts +6 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +9 -0
- package/dist/types/input-file.d.ts +104 -0
- package/dist/types/input-file.d.ts.map +1 -0
- package/dist/types/input-file.js +27 -0
- package/dist/types/kv-store.d.ts +281 -0
- package/dist/types/kv-store.d.ts.map +1 -0
- package/dist/types/kv-store.js +234 -0
- package/dist/types/middleware.d.ts +17 -0
- package/dist/types/middleware.d.ts.map +1 -0
- package/dist/types/middleware.js +21 -0
- package/dist/types/upload-event.d.ts +105 -0
- package/dist/types/upload-event.d.ts.map +1 -0
- package/dist/types/upload-event.js +71 -0
- package/dist/types/upload-file.d.ts +136 -0
- package/dist/types/upload-file.d.ts.map +1 -0
- package/dist/types/upload-file.js +34 -0
- package/dist/types/websocket.d.ts +144 -0
- package/dist/types/websocket.d.ts.map +1 -0
- package/dist/types/websocket.js +40 -0
- package/dist/types-BT-cvi7T.cjs +1 -0
- package/dist/types-DhU2j-XF.js +2 -0
- package/dist/types-DhU2j-XF.js.map +1 -0
- package/dist/upload/convert-to-stream.d.ts +38 -0
- package/dist/upload/convert-to-stream.d.ts.map +1 -0
- package/dist/upload/convert-to-stream.js +43 -0
- package/dist/upload/convert-upload-to-flow-file.d.ts +14 -0
- package/dist/upload/convert-upload-to-flow-file.d.ts.map +1 -0
- package/dist/upload/convert-upload-to-flow-file.js +21 -0
- package/dist/upload/create-upload.d.ts +68 -0
- package/dist/upload/create-upload.d.ts.map +1 -0
- package/dist/upload/create-upload.js +157 -0
- package/dist/upload/index.cjs +1 -0
- package/dist/upload/index.d.cts +6 -0
- package/dist/upload/index.d.ts +4 -0
- package/dist/upload/index.d.ts.map +1 -0
- package/dist/upload/index.js +3 -0
- package/dist/upload/mime.d.ts +24 -0
- package/dist/upload/mime.d.ts.map +1 -0
- package/dist/upload/mime.js +351 -0
- package/dist/upload/upload-chunk.d.ts +58 -0
- package/dist/upload/upload-chunk.d.ts.map +1 -0
- package/dist/upload/upload-chunk.js +277 -0
- package/dist/upload/upload-server.d.ts +221 -0
- package/dist/upload/upload-server.d.ts.map +1 -0
- package/dist/upload/upload-server.js +181 -0
- package/dist/upload/upload-strategy-negotiator.d.ts +148 -0
- package/dist/upload/upload-strategy-negotiator.d.ts.map +1 -0
- package/dist/upload/upload-strategy-negotiator.js +217 -0
- package/dist/upload/upload-url.d.ts +68 -0
- package/dist/upload/upload-url.d.ts.map +1 -0
- package/dist/upload/upload-url.js +142 -0
- package/dist/upload/write-to-store.d.ts +77 -0
- package/dist/upload/write-to-store.d.ts.map +1 -0
- package/dist/upload/write-to-store.js +147 -0
- package/dist/upload-DLuICjpP.cjs +1 -0
- package/dist/upload-DaXO34dE.js +2 -0
- package/dist/upload-DaXO34dE.js.map +1 -0
- package/dist/uploadista-error-BB-Wdiz9.cjs +22 -0
- package/dist/uploadista-error-BVsVxqvz.js +23 -0
- package/dist/uploadista-error-BVsVxqvz.js.map +1 -0
- package/dist/uploadista-error-CwxYs4EB.d.ts +52 -0
- package/dist/uploadista-error-CwxYs4EB.d.ts.map +1 -0
- package/dist/uploadista-error-kKlhLRhY.d.cts +52 -0
- package/dist/uploadista-error-kKlhLRhY.d.cts.map +1 -0
- package/dist/utils/checksum.d.ts +22 -0
- package/dist/utils/checksum.d.ts.map +1 -0
- package/dist/utils/checksum.js +49 -0
- package/dist/utils/debounce.cjs +1 -0
- package/dist/utils/debounce.d.cts +38 -0
- package/dist/utils/debounce.d.cts.map +1 -0
- package/dist/utils/debounce.d.ts +36 -0
- package/dist/utils/debounce.d.ts.map +1 -0
- package/dist/utils/debounce.js +73 -0
- package/dist/utils/generate-id.cjs +1 -0
- package/dist/utils/generate-id.d.cts +2 -0
- package/dist/utils/generate-id.d.ts +32 -0
- package/dist/utils/generate-id.d.ts.map +1 -0
- package/dist/utils/generate-id.js +23 -0
- package/dist/utils/md5.cjs +1 -0
- package/dist/utils/md5.d.cts +73 -0
- package/dist/utils/md5.d.cts.map +1 -0
- package/dist/utils/md5.d.ts +71 -0
- package/dist/utils/md5.d.ts.map +1 -0
- package/dist/utils/md5.js +417 -0
- package/dist/utils/md5.js.map +1 -0
- package/dist/utils/once.cjs +1 -0
- package/dist/utils/once.d.cts +25 -0
- package/dist/utils/once.d.cts.map +1 -0
- package/dist/utils/once.d.ts +21 -0
- package/dist/utils/once.d.ts.map +1 -0
- package/dist/utils/once.js +54 -0
- package/dist/utils/once.js.map +1 -0
- package/dist/utils/semaphore.cjs +1 -0
- package/dist/utils/semaphore.d.cts +3 -0
- package/dist/utils/semaphore.d.ts +78 -0
- package/dist/utils/semaphore.d.ts.map +1 -0
- package/dist/utils/semaphore.js +134 -0
- package/dist/utils/throttle.cjs +1 -0
- package/dist/utils/throttle.d.cts +24 -0
- package/dist/utils/throttle.d.cts.map +1 -0
- package/dist/utils/throttle.d.ts +18 -0
- package/dist/utils/throttle.d.ts.map +1 -0
- package/dist/utils/throttle.js +20 -0
- package/dist/utils/throttle.js.map +1 -0
- package/docs/PARALLEL_EXECUTION.md +206 -0
- package/docs/PARALLEL_EXECUTION_QUICKSTART.md +142 -0
- package/docs/PARALLEL_EXECUTION_REFACTOR.md +184 -0
- package/package.json +80 -0
- package/src/errors/__tests__/uploadista-error.test.ts +251 -0
- package/src/errors/index.ts +2 -0
- package/src/errors/uploadista-error.ts +394 -0
- package/src/flow/README.md +352 -0
- package/src/flow/edge.test.ts +146 -0
- package/src/flow/edge.ts +60 -0
- package/src/flow/event.ts +229 -0
- package/src/flow/flow-server.ts +1089 -0
- package/src/flow/flow.ts +1050 -0
- package/src/flow/index.ts +28 -0
- package/src/flow/node.ts +249 -0
- package/src/flow/nodes/index.ts +8 -0
- package/src/flow/nodes/input-node.ts +296 -0
- package/src/flow/nodes/storage-node.ts +128 -0
- package/src/flow/nodes/transform-node.ts +154 -0
- package/src/flow/parallel-scheduler.ts +259 -0
- package/src/flow/plugins/credential-provider.ts +48 -0
- package/src/flow/plugins/image-ai-plugin.ts +66 -0
- package/src/flow/plugins/image-plugin.ts +60 -0
- package/src/flow/plugins/types/describe-image-node.ts +16 -0
- package/src/flow/plugins/types/index.ts +9 -0
- package/src/flow/plugins/types/optimize-node.ts +18 -0
- package/src/flow/plugins/types/remove-background-node.ts +18 -0
- package/src/flow/plugins/types/resize-node.ts +26 -0
- package/src/flow/plugins/zip-plugin.ts +69 -0
- package/src/flow/typed-flow.ts +279 -0
- package/src/flow/types/flow-file.ts +51 -0
- package/src/flow/types/flow-job.ts +138 -0
- package/src/flow/types/flow-types.ts +353 -0
- package/src/flow/types/index.ts +6 -0
- package/src/flow/types/run-args.ts +40 -0
- package/src/flow/types/type-validator.ts +204 -0
- package/src/flow/utils/resolve-upload-metadata.ts +48 -0
- package/src/index.ts +5 -0
- package/src/logger/logger.ts +14 -0
- package/src/streams/stream-limiter.test.ts +150 -0
- package/src/streams/stream-limiter.ts +75 -0
- package/src/types/data-store.ts +427 -0
- package/src/types/event-broadcaster.ts +39 -0
- package/src/types/event-emitter.ts +349 -0
- package/src/types/index.ts +9 -0
- package/src/types/input-file.ts +107 -0
- package/src/types/kv-store.ts +375 -0
- package/src/types/middleware.ts +54 -0
- package/src/types/upload-event.ts +75 -0
- package/src/types/upload-file.ts +139 -0
- package/src/types/websocket.ts +65 -0
- package/src/upload/convert-to-stream.ts +48 -0
- package/src/upload/create-upload.ts +214 -0
- package/src/upload/index.ts +3 -0
- package/src/upload/mime.ts +436 -0
- package/src/upload/upload-chunk.ts +364 -0
- package/src/upload/upload-server.ts +390 -0
- package/src/upload/upload-strategy-negotiator.ts +316 -0
- package/src/upload/upload-url.ts +173 -0
- package/src/upload/write-to-store.ts +211 -0
- package/src/utils/checksum.ts +61 -0
- package/src/utils/debounce.test.ts +126 -0
- package/src/utils/debounce.ts +89 -0
- package/src/utils/generate-id.ts +35 -0
- package/src/utils/md5.ts +475 -0
- package/src/utils/once.test.ts +83 -0
- package/src/utils/once.ts +63 -0
- package/src/utils/throttle.test.ts +101 -0
- package/src/utils/throttle.ts +29 -0
- package/tsconfig.json +20 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/tsdown.config.ts +25 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
import { Context, Effect, Layer } from "effect";
|
|
2
|
+
import { UploadistaError } from "../errors";
|
|
3
|
+
import { createFlowWithSchema, EventType, getFlowData, runArgsSchema, } from "../flow";
|
|
4
|
+
import { FlowEventEmitter, FlowJobKVStore } from "../types";
|
|
5
|
+
import { UploadServer } from "../upload";
|
|
6
|
+
/**
|
|
7
|
+
* Effect-TS context tag for the FlowProvider service.
|
|
8
|
+
*
|
|
9
|
+
* Applications must provide an implementation of FlowProviderShape
|
|
10
|
+
* to enable the FlowServer to retrieve flow definitions.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* // Access FlowProvider in an Effect
|
|
15
|
+
* const effect = Effect.gen(function* () {
|
|
16
|
+
* const provider = yield* FlowProvider;
|
|
17
|
+
* const flow = yield* provider.getFlow("flow123", "client456");
|
|
18
|
+
* return flow;
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export class FlowProvider extends Context.Tag("FlowProvider")() {
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Effect-TS context tag for the FlowServer service.
|
|
26
|
+
*
|
|
27
|
+
* Use this tag to access the FlowServer in an Effect context.
|
|
28
|
+
* The server must be provided via a Layer or dependency injection.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* // Access FlowServer in an Effect
|
|
33
|
+
* const flowEffect = Effect.gen(function* () {
|
|
34
|
+
* const server = yield* FlowServer;
|
|
35
|
+
* const job = yield* server.runFlow({
|
|
36
|
+
* flowId: "my-flow",
|
|
37
|
+
* storageId: "s3",
|
|
38
|
+
* clientId: null,
|
|
39
|
+
* inputs: {}
|
|
40
|
+
* });
|
|
41
|
+
* return job;
|
|
42
|
+
* });
|
|
43
|
+
*
|
|
44
|
+
* // Provide FlowServer layer
|
|
45
|
+
* const program = flowEffect.pipe(
|
|
46
|
+
* Effect.provide(flowServer),
|
|
47
|
+
* Effect.provide(flowProviderLayer),
|
|
48
|
+
* Effect.provide(flowJobKvStore)
|
|
49
|
+
* );
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export class FlowServer extends Context.Tag("FlowServer")() {
|
|
53
|
+
}
|
|
54
|
+
const isResultUploadFile = (result) => {
|
|
55
|
+
return typeof result === "object" && result !== null && "id" in result;
|
|
56
|
+
};
|
|
57
|
+
// Function to enhance a flow with event emission capabilities
|
|
58
|
+
function withFlowEvents(flow, eventEmitter, kvStore) {
|
|
59
|
+
// Shared helper to create onEvent callback for a given jobId
|
|
60
|
+
const createOnEventCallback = (executionJobId) => {
|
|
61
|
+
// Helper to update job in KV store
|
|
62
|
+
const updateJobInStore = (updates) => Effect.gen(function* () {
|
|
63
|
+
const job = yield* kvStore.get(executionJobId);
|
|
64
|
+
if (job) {
|
|
65
|
+
yield* kvStore.set(executionJobId, {
|
|
66
|
+
...job,
|
|
67
|
+
...updates,
|
|
68
|
+
updatedAt: new Date(),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
// Create the onEvent callback that calls original onEvent, emits to eventEmitter, and updates job
|
|
73
|
+
return (event) => Effect.gen(function* () {
|
|
74
|
+
// Call the original onEvent from the flow if it exists
|
|
75
|
+
// Catch errors to prevent them from blocking flow execution
|
|
76
|
+
if (flow.onEvent) {
|
|
77
|
+
yield* Effect.catchAll(flow.onEvent(event), (error) => {
|
|
78
|
+
// Log the error but don't fail the flow
|
|
79
|
+
Effect.logError("Original onEvent failed", error);
|
|
80
|
+
return Effect.succeed({ eventId: null });
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
// Emit event
|
|
84
|
+
yield* eventEmitter.emit(executionJobId, event);
|
|
85
|
+
Effect.logInfo(`Updating job ${executionJobId} with event ${event.eventType}`);
|
|
86
|
+
// Update job based on event type
|
|
87
|
+
switch (event.eventType) {
|
|
88
|
+
case EventType.FlowStart:
|
|
89
|
+
yield* updateJobInStore({ status: "running" });
|
|
90
|
+
break;
|
|
91
|
+
case EventType.FlowEnd:
|
|
92
|
+
// Flow end is handled by executeFlowInBackground
|
|
93
|
+
// This case ensures the event is still emitted
|
|
94
|
+
break;
|
|
95
|
+
case EventType.FlowError:
|
|
96
|
+
yield* updateJobInStore({
|
|
97
|
+
status: "failed",
|
|
98
|
+
error: event.error,
|
|
99
|
+
});
|
|
100
|
+
break;
|
|
101
|
+
case EventType.NodeStart:
|
|
102
|
+
yield* Effect.gen(function* () {
|
|
103
|
+
const job = yield* kvStore.get(executionJobId);
|
|
104
|
+
if (job) {
|
|
105
|
+
const existingTask = job.tasks.find((t) => t.nodeId === event.nodeId);
|
|
106
|
+
const updatedTasks = existingTask
|
|
107
|
+
? job.tasks.map((t) => t.nodeId === event.nodeId
|
|
108
|
+
? {
|
|
109
|
+
...t,
|
|
110
|
+
status: "running",
|
|
111
|
+
updatedAt: new Date(),
|
|
112
|
+
}
|
|
113
|
+
: t)
|
|
114
|
+
: [
|
|
115
|
+
...job.tasks,
|
|
116
|
+
{
|
|
117
|
+
nodeId: event.nodeId,
|
|
118
|
+
status: "running",
|
|
119
|
+
createdAt: new Date(),
|
|
120
|
+
updatedAt: new Date(),
|
|
121
|
+
},
|
|
122
|
+
];
|
|
123
|
+
yield* kvStore.set(executionJobId, {
|
|
124
|
+
...job,
|
|
125
|
+
tasks: updatedTasks,
|
|
126
|
+
updatedAt: new Date(),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
break;
|
|
131
|
+
case EventType.NodePause:
|
|
132
|
+
yield* Effect.gen(function* () {
|
|
133
|
+
const job = yield* kvStore.get(executionJobId);
|
|
134
|
+
if (job) {
|
|
135
|
+
const existingTask = job.tasks.find((t) => t.nodeId === event.nodeId);
|
|
136
|
+
const updatedTasks = existingTask
|
|
137
|
+
? job.tasks.map((t) => t.nodeId === event.nodeId
|
|
138
|
+
? {
|
|
139
|
+
...t,
|
|
140
|
+
status: "paused",
|
|
141
|
+
result: event.partialData,
|
|
142
|
+
updatedAt: new Date(),
|
|
143
|
+
}
|
|
144
|
+
: t)
|
|
145
|
+
: [
|
|
146
|
+
...job.tasks,
|
|
147
|
+
{
|
|
148
|
+
nodeId: event.nodeId,
|
|
149
|
+
status: "paused",
|
|
150
|
+
result: event.partialData,
|
|
151
|
+
createdAt: new Date(),
|
|
152
|
+
updatedAt: new Date(),
|
|
153
|
+
},
|
|
154
|
+
];
|
|
155
|
+
yield* kvStore.set(executionJobId, {
|
|
156
|
+
...job,
|
|
157
|
+
tasks: updatedTasks,
|
|
158
|
+
updatedAt: new Date(),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
break;
|
|
163
|
+
case EventType.NodeResume:
|
|
164
|
+
yield* Effect.gen(function* () {
|
|
165
|
+
const job = yield* kvStore.get(executionJobId);
|
|
166
|
+
if (job) {
|
|
167
|
+
const updatedTasks = job.tasks.map((t) => t.nodeId === event.nodeId
|
|
168
|
+
? {
|
|
169
|
+
...t,
|
|
170
|
+
status: "running",
|
|
171
|
+
updatedAt: new Date(),
|
|
172
|
+
}
|
|
173
|
+
: t);
|
|
174
|
+
yield* kvStore.set(executionJobId, {
|
|
175
|
+
...job,
|
|
176
|
+
tasks: updatedTasks,
|
|
177
|
+
updatedAt: new Date(),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
break;
|
|
182
|
+
case EventType.NodeEnd:
|
|
183
|
+
yield* Effect.gen(function* () {
|
|
184
|
+
const job = yield* kvStore.get(executionJobId);
|
|
185
|
+
if (job) {
|
|
186
|
+
const updatedTasks = job.tasks.map((t) => t.nodeId === event.nodeId
|
|
187
|
+
? {
|
|
188
|
+
...t,
|
|
189
|
+
status: "completed",
|
|
190
|
+
result: event.result,
|
|
191
|
+
updatedAt: new Date(),
|
|
192
|
+
}
|
|
193
|
+
: t);
|
|
194
|
+
// Track intermediate files for cleanup
|
|
195
|
+
// Check if result is an UploadFile and node is not an output node
|
|
196
|
+
const node = flow.nodes.find((n) => n.id === event.nodeId);
|
|
197
|
+
const isOutputNode = node?.type === "output";
|
|
198
|
+
const result = event.result;
|
|
199
|
+
let intermediateFiles = job.intermediateFiles || [];
|
|
200
|
+
if (isOutputNode && isResultUploadFile(result) && result.id) {
|
|
201
|
+
// If this is an output node and it returns a file that was an intermediate file,
|
|
202
|
+
// remove it from the intermediate files list (it's now the final output)
|
|
203
|
+
intermediateFiles = intermediateFiles.filter((fileId) => fileId !== result.id);
|
|
204
|
+
}
|
|
205
|
+
else if (!isOutputNode &&
|
|
206
|
+
isResultUploadFile(result) &&
|
|
207
|
+
result.id) {
|
|
208
|
+
// Only add to intermediate files if it's not an output node
|
|
209
|
+
if (!intermediateFiles.includes(result.id)) {
|
|
210
|
+
intermediateFiles.push(result.id);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
yield* kvStore.set(executionJobId, {
|
|
214
|
+
...job,
|
|
215
|
+
tasks: updatedTasks,
|
|
216
|
+
intermediateFiles,
|
|
217
|
+
updatedAt: new Date(),
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
break;
|
|
222
|
+
case EventType.NodeError:
|
|
223
|
+
yield* Effect.gen(function* () {
|
|
224
|
+
const job = yield* kvStore.get(executionJobId);
|
|
225
|
+
if (job) {
|
|
226
|
+
const updatedTasks = job.tasks.map((t) => t.nodeId === event.nodeId
|
|
227
|
+
? {
|
|
228
|
+
...t,
|
|
229
|
+
status: "failed",
|
|
230
|
+
error: event.error,
|
|
231
|
+
retryCount: event.retryCount,
|
|
232
|
+
updatedAt: new Date(),
|
|
233
|
+
}
|
|
234
|
+
: t);
|
|
235
|
+
yield* kvStore.set(executionJobId, {
|
|
236
|
+
...job,
|
|
237
|
+
tasks: updatedTasks,
|
|
238
|
+
error: event.error,
|
|
239
|
+
updatedAt: new Date(),
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
return { eventId: executionJobId };
|
|
246
|
+
});
|
|
247
|
+
};
|
|
248
|
+
return {
|
|
249
|
+
...flow,
|
|
250
|
+
run: (args) => {
|
|
251
|
+
return Effect.gen(function* () {
|
|
252
|
+
// Use provided jobId or generate a new one
|
|
253
|
+
const executionJobId = args.jobId || crypto.randomUUID();
|
|
254
|
+
const onEventCallback = createOnEventCallback(executionJobId);
|
|
255
|
+
// Create a new flow with the same configuration but with onEvent callback
|
|
256
|
+
const flowWithEvents = yield* createFlowWithSchema({
|
|
257
|
+
flowId: flow.id,
|
|
258
|
+
name: flow.name,
|
|
259
|
+
nodes: flow.nodes,
|
|
260
|
+
edges: flow.edges,
|
|
261
|
+
inputSchema: flow.inputSchema,
|
|
262
|
+
outputSchema: flow.outputSchema,
|
|
263
|
+
onEvent: onEventCallback,
|
|
264
|
+
});
|
|
265
|
+
// Run the enhanced flow with consistent jobId
|
|
266
|
+
const result = yield* flowWithEvents.run({
|
|
267
|
+
...args,
|
|
268
|
+
jobId: executionJobId,
|
|
269
|
+
clientId: args.clientId,
|
|
270
|
+
});
|
|
271
|
+
// Return the result directly (can be completed or paused)
|
|
272
|
+
return result;
|
|
273
|
+
});
|
|
274
|
+
},
|
|
275
|
+
resume: (args) => {
|
|
276
|
+
return Effect.gen(function* () {
|
|
277
|
+
const executionJobId = args.jobId;
|
|
278
|
+
const onEventCallback = createOnEventCallback(executionJobId);
|
|
279
|
+
// Create a new flow with the same configuration but with onEvent callback
|
|
280
|
+
const flowWithEvents = yield* createFlowWithSchema({
|
|
281
|
+
flowId: flow.id,
|
|
282
|
+
name: flow.name,
|
|
283
|
+
nodes: flow.nodes,
|
|
284
|
+
edges: flow.edges,
|
|
285
|
+
inputSchema: flow.inputSchema,
|
|
286
|
+
outputSchema: flow.outputSchema,
|
|
287
|
+
onEvent: onEventCallback,
|
|
288
|
+
});
|
|
289
|
+
// Resume the enhanced flow
|
|
290
|
+
const result = yield* flowWithEvents.resume(args);
|
|
291
|
+
// Return the result directly (can be completed or paused)
|
|
292
|
+
return result;
|
|
293
|
+
});
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
// Core FlowServer implementation
|
|
298
|
+
export function createFlowServer() {
|
|
299
|
+
return Effect.gen(function* () {
|
|
300
|
+
const flowProvider = yield* FlowProvider;
|
|
301
|
+
const eventEmitter = yield* FlowEventEmitter;
|
|
302
|
+
const kvStore = yield* FlowJobKVStore;
|
|
303
|
+
const uploadServer = yield* UploadServer;
|
|
304
|
+
const updateJob = (jobId, updates) => Effect.gen(function* () {
|
|
305
|
+
const job = yield* kvStore.get(jobId);
|
|
306
|
+
if (!job) {
|
|
307
|
+
return yield* Effect.fail(UploadistaError.fromCode("FLOW_JOB_NOT_FOUND", {
|
|
308
|
+
cause: `Job ${jobId} not found`,
|
|
309
|
+
}));
|
|
310
|
+
}
|
|
311
|
+
return yield* kvStore.set(jobId, { ...job, ...updates });
|
|
312
|
+
});
|
|
313
|
+
// Helper function to cleanup intermediate files
|
|
314
|
+
const cleanupIntermediateFiles = (jobId, clientId) => Effect.gen(function* () {
|
|
315
|
+
const job = yield* kvStore.get(jobId);
|
|
316
|
+
if (!job ||
|
|
317
|
+
!job.intermediateFiles ||
|
|
318
|
+
job.intermediateFiles.length === 0) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
yield* Effect.logInfo(`Cleaning up ${job.intermediateFiles.length} intermediate files for job ${jobId}`);
|
|
322
|
+
// Delete each intermediate file
|
|
323
|
+
yield* Effect.all(job.intermediateFiles.map((fileId) => Effect.gen(function* () {
|
|
324
|
+
yield* uploadServer.delete(fileId, clientId);
|
|
325
|
+
yield* Effect.logDebug(`Deleted intermediate file ${fileId}`);
|
|
326
|
+
}).pipe(Effect.catchAll((error) => Effect.gen(function* () {
|
|
327
|
+
yield* Effect.logWarning(`Failed to delete intermediate file ${fileId}: ${error}`);
|
|
328
|
+
return Effect.succeed(undefined);
|
|
329
|
+
})))), { concurrency: 5 });
|
|
330
|
+
// Clear the intermediateFiles array
|
|
331
|
+
yield* updateJob(jobId, {
|
|
332
|
+
intermediateFiles: [],
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
// Helper function to execute flow in background
|
|
336
|
+
const executeFlowInBackground = ({ jobId, flow, storageId, clientId, inputs, }) => Effect.gen(function* () {
|
|
337
|
+
// Update job status to running
|
|
338
|
+
yield* updateJob(jobId, {
|
|
339
|
+
status: "running",
|
|
340
|
+
});
|
|
341
|
+
const flowWithEvents = withFlowEvents(flow, eventEmitter, kvStore);
|
|
342
|
+
// Run the flow with the consistent jobId
|
|
343
|
+
const result = yield* flowWithEvents.run({
|
|
344
|
+
inputs,
|
|
345
|
+
storageId,
|
|
346
|
+
jobId,
|
|
347
|
+
clientId,
|
|
348
|
+
});
|
|
349
|
+
// Handle result based on type
|
|
350
|
+
if (result.type === "paused") {
|
|
351
|
+
// Update job as paused (node results are in tasks, not executionState)
|
|
352
|
+
yield* updateJob(jobId, {
|
|
353
|
+
status: "paused",
|
|
354
|
+
pausedAt: result.nodeId,
|
|
355
|
+
executionState: result.executionState,
|
|
356
|
+
updatedAt: new Date(),
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
// Update job as completed with final result
|
|
361
|
+
yield* updateJob(jobId, {
|
|
362
|
+
status: "completed",
|
|
363
|
+
result: result.result,
|
|
364
|
+
updatedAt: new Date(),
|
|
365
|
+
endedAt: new Date(),
|
|
366
|
+
});
|
|
367
|
+
// Cleanup intermediate files
|
|
368
|
+
yield* cleanupIntermediateFiles(jobId, clientId);
|
|
369
|
+
}
|
|
370
|
+
return result;
|
|
371
|
+
}).pipe(Effect.catchAll((error) => Effect.gen(function* () {
|
|
372
|
+
yield* Effect.logError("Flow execution failed", error);
|
|
373
|
+
// Convert error to a proper message
|
|
374
|
+
const errorMessage = error instanceof UploadistaError ? error.body : String(error);
|
|
375
|
+
yield* Effect.logInfo(`Updating job ${jobId} to failed status with error: ${errorMessage}`);
|
|
376
|
+
// Update job as failed - do this FIRST before cleanup
|
|
377
|
+
yield* updateJob(jobId, {
|
|
378
|
+
status: "failed",
|
|
379
|
+
error: errorMessage,
|
|
380
|
+
updatedAt: new Date(),
|
|
381
|
+
}).pipe(Effect.catchAll((updateError) => Effect.gen(function* () {
|
|
382
|
+
yield* Effect.logError(`Failed to update job ${jobId}`, updateError);
|
|
383
|
+
return Effect.succeed(undefined);
|
|
384
|
+
})));
|
|
385
|
+
// Emit FlowError event to notify client
|
|
386
|
+
const job = yield* kvStore.get(jobId);
|
|
387
|
+
if (job) {
|
|
388
|
+
yield* eventEmitter
|
|
389
|
+
.emit(jobId, {
|
|
390
|
+
jobId,
|
|
391
|
+
eventType: EventType.FlowError,
|
|
392
|
+
flowId: job.flowId,
|
|
393
|
+
error: errorMessage,
|
|
394
|
+
})
|
|
395
|
+
.pipe(Effect.catchAll((emitError) => Effect.gen(function* () {
|
|
396
|
+
yield* Effect.logError(`Failed to emit FlowError event for job ${jobId}`, emitError);
|
|
397
|
+
return Effect.succeed(undefined);
|
|
398
|
+
})));
|
|
399
|
+
}
|
|
400
|
+
// Cleanup intermediate files even on failure (don't let this fail the error handling)
|
|
401
|
+
yield* cleanupIntermediateFiles(jobId, clientId).pipe(Effect.catchAll((cleanupError) => Effect.gen(function* () {
|
|
402
|
+
yield* Effect.logWarning(`Failed to cleanup intermediate files for job ${jobId}`, cleanupError);
|
|
403
|
+
return Effect.succeed(undefined);
|
|
404
|
+
})));
|
|
405
|
+
return Effect.fail(error);
|
|
406
|
+
})));
|
|
407
|
+
return {
|
|
408
|
+
getFlow: (flowId, clientId) => Effect.gen(function* () {
|
|
409
|
+
const flow = yield* flowProvider.getFlow(flowId, clientId);
|
|
410
|
+
return flow;
|
|
411
|
+
}),
|
|
412
|
+
getFlowData: (flowId, clientId) => Effect.gen(function* () {
|
|
413
|
+
const flow = yield* flowProvider.getFlow(flowId, clientId);
|
|
414
|
+
return getFlowData(flow);
|
|
415
|
+
}),
|
|
416
|
+
runFlow: ({ flowId, storageId, clientId, inputs, }) => Effect.gen(function* () {
|
|
417
|
+
const parsedParams = yield* Effect.try({
|
|
418
|
+
try: () => runArgsSchema.parse({ inputs }),
|
|
419
|
+
catch: (error) => UploadistaError.fromCode("FLOW_INPUT_VALIDATION_ERROR", {
|
|
420
|
+
cause: error,
|
|
421
|
+
}),
|
|
422
|
+
});
|
|
423
|
+
// Generate a unique jobId
|
|
424
|
+
const jobId = crypto.randomUUID();
|
|
425
|
+
const createdAt = new Date();
|
|
426
|
+
// Store initial job metadata
|
|
427
|
+
const job = {
|
|
428
|
+
id: jobId,
|
|
429
|
+
flowId,
|
|
430
|
+
storageId,
|
|
431
|
+
clientId,
|
|
432
|
+
status: "started",
|
|
433
|
+
createdAt,
|
|
434
|
+
updatedAt: createdAt,
|
|
435
|
+
tasks: [],
|
|
436
|
+
};
|
|
437
|
+
yield* kvStore.set(jobId, job);
|
|
438
|
+
// Get the flow and start background execution
|
|
439
|
+
const flow = yield* flowProvider.getFlow(flowId, clientId);
|
|
440
|
+
// Fork the flow execution to run in background as daemon
|
|
441
|
+
yield* Effect.forkDaemon(executeFlowInBackground({
|
|
442
|
+
jobId,
|
|
443
|
+
flow,
|
|
444
|
+
storageId,
|
|
445
|
+
clientId,
|
|
446
|
+
inputs: parsedParams.inputs,
|
|
447
|
+
}).pipe(Effect.tapErrorCause((cause) => Effect.logError("Flow execution failed", cause))));
|
|
448
|
+
// Return immediately with jobId
|
|
449
|
+
return job;
|
|
450
|
+
}),
|
|
451
|
+
getJobStatus: (jobId) => Effect.gen(function* () {
|
|
452
|
+
const job = yield* kvStore.get(jobId);
|
|
453
|
+
if (!job) {
|
|
454
|
+
return yield* Effect.fail(UploadistaError.fromCode("FLOW_JOB_NOT_FOUND", {
|
|
455
|
+
cause: `Job ${jobId} not found`,
|
|
456
|
+
}));
|
|
457
|
+
}
|
|
458
|
+
return job;
|
|
459
|
+
}),
|
|
460
|
+
continueFlow: ({ jobId, nodeId, newData, clientId, }) => Effect.gen(function* () {
|
|
461
|
+
console.log("continueFlow", jobId, nodeId, newData);
|
|
462
|
+
// Get the current job
|
|
463
|
+
const job = yield* kvStore.get(jobId);
|
|
464
|
+
if (!job) {
|
|
465
|
+
console.error("Job not found");
|
|
466
|
+
return yield* Effect.fail(UploadistaError.fromCode("FLOW_JOB_NOT_FOUND", {
|
|
467
|
+
cause: `Job ${jobId} not found`,
|
|
468
|
+
}));
|
|
469
|
+
}
|
|
470
|
+
// Verify job is paused
|
|
471
|
+
if (job.status !== "paused") {
|
|
472
|
+
console.error("Job is not paused");
|
|
473
|
+
return yield* Effect.fail(UploadistaError.fromCode("FLOW_JOB_ERROR", {
|
|
474
|
+
cause: `Job ${jobId} is not paused (status: ${job.status})`,
|
|
475
|
+
}));
|
|
476
|
+
}
|
|
477
|
+
// Verify it's paused at the expected node
|
|
478
|
+
if (job.pausedAt !== nodeId) {
|
|
479
|
+
console.error("Job is not paused at the expected node");
|
|
480
|
+
return yield* Effect.fail(UploadistaError.fromCode("FLOW_JOB_ERROR", {
|
|
481
|
+
cause: `Job ${jobId} is paused at node ${job.pausedAt}, not ${nodeId}`,
|
|
482
|
+
}));
|
|
483
|
+
}
|
|
484
|
+
// Verify we have execution state
|
|
485
|
+
if (!job.executionState) {
|
|
486
|
+
console.error("Job has no execution state");
|
|
487
|
+
return yield* Effect.fail(UploadistaError.fromCode("FLOW_JOB_ERROR", {
|
|
488
|
+
cause: `Job ${jobId} has no execution state`,
|
|
489
|
+
}));
|
|
490
|
+
}
|
|
491
|
+
// Reconstruct nodeResults from tasks
|
|
492
|
+
const nodeResults = job.tasks.reduce((acc, task) => {
|
|
493
|
+
if (task.result !== undefined) {
|
|
494
|
+
acc[task.nodeId] = task.result;
|
|
495
|
+
}
|
|
496
|
+
return acc;
|
|
497
|
+
}, {});
|
|
498
|
+
// Update with new data
|
|
499
|
+
const updatedNodeResults = {
|
|
500
|
+
...nodeResults,
|
|
501
|
+
[nodeId]: newData,
|
|
502
|
+
};
|
|
503
|
+
const updatedInputs = {
|
|
504
|
+
...job.executionState.inputs,
|
|
505
|
+
[nodeId]: newData,
|
|
506
|
+
};
|
|
507
|
+
// Update job status to running BEFORE forking background execution
|
|
508
|
+
// This ensures the status is updated synchronously before events start firing
|
|
509
|
+
yield* updateJob(jobId, {
|
|
510
|
+
status: "running",
|
|
511
|
+
});
|
|
512
|
+
// Get the flow
|
|
513
|
+
const flow = yield* flowProvider.getFlow(job.flowId, job.clientId);
|
|
514
|
+
// Helper to resume flow in background
|
|
515
|
+
const resumeFlowInBackground = Effect.gen(function* () {
|
|
516
|
+
const flowWithEvents = withFlowEvents(flow, eventEmitter, kvStore);
|
|
517
|
+
if (!job.executionState) {
|
|
518
|
+
return yield* Effect.fail(UploadistaError.fromCode("FLOW_JOB_ERROR", {
|
|
519
|
+
cause: `Job ${jobId} has no execution state`,
|
|
520
|
+
}));
|
|
521
|
+
}
|
|
522
|
+
// Resume the flow with updated state
|
|
523
|
+
const result = yield* flowWithEvents.resume({
|
|
524
|
+
jobId,
|
|
525
|
+
storageId: job.storageId,
|
|
526
|
+
nodeResults: updatedNodeResults,
|
|
527
|
+
executionState: {
|
|
528
|
+
...job.executionState,
|
|
529
|
+
inputs: updatedInputs,
|
|
530
|
+
},
|
|
531
|
+
clientId: job.clientId,
|
|
532
|
+
});
|
|
533
|
+
// Handle result based on type
|
|
534
|
+
if (result.type === "paused") {
|
|
535
|
+
// Update job as paused again (node results are in tasks, not executionState)
|
|
536
|
+
yield* updateJob(jobId, {
|
|
537
|
+
status: "paused",
|
|
538
|
+
pausedAt: result.nodeId,
|
|
539
|
+
executionState: result.executionState,
|
|
540
|
+
updatedAt: new Date(),
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
else {
|
|
544
|
+
// Update job as completed with final result
|
|
545
|
+
yield* updateJob(jobId, {
|
|
546
|
+
status: "completed",
|
|
547
|
+
pausedAt: undefined,
|
|
548
|
+
executionState: undefined,
|
|
549
|
+
result: result.result,
|
|
550
|
+
updatedAt: new Date(),
|
|
551
|
+
endedAt: new Date(),
|
|
552
|
+
});
|
|
553
|
+
// Cleanup intermediate files
|
|
554
|
+
yield* cleanupIntermediateFiles(jobId, clientId);
|
|
555
|
+
}
|
|
556
|
+
return result;
|
|
557
|
+
}).pipe(Effect.catchAll((error) => Effect.gen(function* () {
|
|
558
|
+
yield* Effect.logError("Flow resume failed", error);
|
|
559
|
+
// Convert error to a proper message
|
|
560
|
+
const errorMessage = error instanceof UploadistaError ? error.body : String(error);
|
|
561
|
+
yield* Effect.logInfo(`Updating job ${jobId} to failed status with error: ${errorMessage}`);
|
|
562
|
+
// Update job as failed - do this FIRST before cleanup
|
|
563
|
+
yield* updateJob(jobId, {
|
|
564
|
+
status: "failed",
|
|
565
|
+
error: errorMessage,
|
|
566
|
+
updatedAt: new Date(),
|
|
567
|
+
}).pipe(Effect.catchAll((updateError) => Effect.gen(function* () {
|
|
568
|
+
yield* Effect.logError(`Failed to update job ${jobId}`, updateError);
|
|
569
|
+
return Effect.succeed(undefined);
|
|
570
|
+
})));
|
|
571
|
+
// Emit FlowError event to notify client
|
|
572
|
+
const currentJob = yield* kvStore.get(jobId);
|
|
573
|
+
if (currentJob) {
|
|
574
|
+
yield* eventEmitter
|
|
575
|
+
.emit(jobId, {
|
|
576
|
+
jobId,
|
|
577
|
+
eventType: EventType.FlowError,
|
|
578
|
+
flowId: currentJob.flowId,
|
|
579
|
+
error: errorMessage,
|
|
580
|
+
})
|
|
581
|
+
.pipe(Effect.catchAll((emitError) => Effect.gen(function* () {
|
|
582
|
+
yield* Effect.logError(`Failed to emit FlowError event for job ${jobId}`, emitError);
|
|
583
|
+
return Effect.succeed(undefined);
|
|
584
|
+
})));
|
|
585
|
+
}
|
|
586
|
+
// Cleanup intermediate files even on failure (don't let this fail the error handling)
|
|
587
|
+
yield* cleanupIntermediateFiles(jobId, clientId).pipe(Effect.catchAll((cleanupError) => Effect.gen(function* () {
|
|
588
|
+
yield* Effect.logWarning(`Failed to cleanup intermediate files for job ${jobId}`, cleanupError);
|
|
589
|
+
return Effect.succeed(undefined);
|
|
590
|
+
})));
|
|
591
|
+
return Effect.fail(error);
|
|
592
|
+
})));
|
|
593
|
+
// Fork the resume execution to run in background as daemon
|
|
594
|
+
yield* Effect.forkDaemon(resumeFlowInBackground.pipe(Effect.tapErrorCause((cause) => Effect.logError("Flow resume failed", cause))));
|
|
595
|
+
// Return immediately with updated job
|
|
596
|
+
const updatedJob = yield* kvStore.get(jobId);
|
|
597
|
+
if (!updatedJob) {
|
|
598
|
+
return yield* Effect.fail(UploadistaError.fromCode("FLOW_JOB_NOT_FOUND", {
|
|
599
|
+
cause: `Job ${jobId} not found after update`,
|
|
600
|
+
}));
|
|
601
|
+
}
|
|
602
|
+
return updatedJob;
|
|
603
|
+
}),
|
|
604
|
+
subscribeToFlowEvents: (jobId, connection) => Effect.gen(function* () {
|
|
605
|
+
yield* eventEmitter.subscribe(jobId, connection);
|
|
606
|
+
}),
|
|
607
|
+
unsubscribeFromFlowEvents: (jobId) => Effect.gen(function* () {
|
|
608
|
+
yield* eventEmitter.unsubscribe(jobId);
|
|
609
|
+
}),
|
|
610
|
+
};
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
// Export the FlowServer layer with job store dependency
|
|
614
|
+
export const flowServer = Layer.effect(FlowServer, createFlowServer());
|