@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,142 @@
|
|
|
1
|
+
# Parallel Execution - Quick Start Guide
|
|
2
|
+
|
|
3
|
+
## TL;DR
|
|
4
|
+
|
|
5
|
+
The `ParallelScheduler` now uses Effect's native concurrency primitives instead of broken Semaphore dependencies. Enable parallel execution with one config option.
|
|
6
|
+
|
|
7
|
+
## Enable Parallel Execution
|
|
8
|
+
|
|
9
|
+
Add `parallelExecution` to your flow config:
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
const flow = yield* createFlowWithSchema({
|
|
13
|
+
flowId: 'my-flow',
|
|
14
|
+
name: 'My Flow',
|
|
15
|
+
nodes: [inputNode, processA, processB, mergeNode, outputNode],
|
|
16
|
+
edges: [
|
|
17
|
+
{ source: 'input', target: 'processA' },
|
|
18
|
+
{ source: 'input', target: 'processB' },
|
|
19
|
+
{ source: 'processA', target: 'merge' },
|
|
20
|
+
{ source: 'processB', target: 'merge' },
|
|
21
|
+
{ source: 'merge', target: 'output' }
|
|
22
|
+
],
|
|
23
|
+
inputSchema: myInputSchema,
|
|
24
|
+
outputSchema: myOutputSchema,
|
|
25
|
+
parallelExecution: {
|
|
26
|
+
enabled: true,
|
|
27
|
+
maxConcurrency: 4,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## How It Works
|
|
33
|
+
|
|
34
|
+
1. **Dependency Analysis**: Nodes are automatically grouped by their dependencies
|
|
35
|
+
2. **Level-Based Execution**:
|
|
36
|
+
- Nodes with no dependencies (Level 0)
|
|
37
|
+
- Nodes depending on Level 0 (Level 1)
|
|
38
|
+
- And so on...
|
|
39
|
+
3. **Parallel Within Levels**: Nodes at the same level run in parallel
|
|
40
|
+
4. **Sequential Between Levels**: Levels run one after another
|
|
41
|
+
|
|
42
|
+
### Example Flow
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
Input → [Resize ║ Optimize] → Merge → Output
|
|
46
|
+
└─ Level 1 (parallel) ─┘
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- Input runs first
|
|
50
|
+
- Resize and Optimize run in parallel
|
|
51
|
+
- Merge waits for both to complete
|
|
52
|
+
- Output runs last
|
|
53
|
+
|
|
54
|
+
## Configuration
|
|
55
|
+
|
|
56
|
+
| Option | Default | Description |
|
|
57
|
+
|--------|---------|-------------|
|
|
58
|
+
| `enabled` | `false` | Enable/disable parallel execution |
|
|
59
|
+
| `maxConcurrency` | `4` | Max concurrent nodes per level |
|
|
60
|
+
|
|
61
|
+
## What Changed
|
|
62
|
+
|
|
63
|
+
### ParallelScheduler
|
|
64
|
+
|
|
65
|
+
**Before** (broken):
|
|
66
|
+
```typescript
|
|
67
|
+
// This didn't work - Semaphore doesn't exist
|
|
68
|
+
const resourceSemaphore = semaphore(maxConcurrency); // ERROR
|
|
69
|
+
async executeNodesInParallel(executors) { ... }
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**After** (working):
|
|
73
|
+
```typescript
|
|
74
|
+
// Uses Effect's Effect.all() with concurrency option
|
|
75
|
+
executeNodesInParallel<T, E, R>(
|
|
76
|
+
nodeExecutors: Array<() => Effect.Effect<T, E, R>>,
|
|
77
|
+
): Effect.Effect<T[], E, R> {
|
|
78
|
+
return Effect.all(nodeExecutors.map((executor) => executor()), {
|
|
79
|
+
concurrency: this.maxConcurrency,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Flow Engine
|
|
85
|
+
|
|
86
|
+
Automatic detection of `parallelExecution.enabled`:
|
|
87
|
+
- If `true`: Uses `ParallelScheduler` with level-based execution
|
|
88
|
+
- If `false` (default): Uses original sequential execution
|
|
89
|
+
|
|
90
|
+
## Performance Example
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
Sequential: Input(1s) → Resize(2s) → Optimize(2s) → Merge(1s) → Output(1s) = 7s
|
|
94
|
+
Parallel: Input(1s) → [Resize(2s) ║ Optimize(2s)] → Merge(1s) → Output(1s) = 5s
|
|
95
|
+
Speedup: 28% faster
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Debugging
|
|
99
|
+
|
|
100
|
+
Enable debug logs to see execution levels:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
Flow my-flow: Executing in parallel mode (maxConcurrency: 4)
|
|
104
|
+
Flow my-flow: Grouped nodes into 4 execution levels
|
|
105
|
+
Flow my-flow: Executing level 0 with nodes: input_1
|
|
106
|
+
Flow my-flow: Executing level 1 with nodes: resize_1, optimize_1
|
|
107
|
+
Flow my-flow: Executing level 2 with nodes: merge_1
|
|
108
|
+
Flow my-flow: Executing level 3 with nodes: output_1
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Introspection
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
const scheduler = new ParallelScheduler({ maxConcurrency: 4 });
|
|
115
|
+
|
|
116
|
+
// See how nodes are grouped
|
|
117
|
+
const levels = scheduler.groupNodesByExecutionLevel(nodes, edges);
|
|
118
|
+
levels.forEach(level => {
|
|
119
|
+
console.log(`Level ${level.level}: ${level.nodes.join(', ')}`);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Get stats
|
|
123
|
+
const stats = scheduler.getStats();
|
|
124
|
+
console.log(`Max concurrency: ${stats.maxConcurrency}`);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Backward Compatibility
|
|
128
|
+
|
|
129
|
+
✅ Existing flows work unchanged (parallel execution disabled by default)
|
|
130
|
+
✅ No breaking changes
|
|
131
|
+
✅ Add config option to opt-in to parallel execution
|
|
132
|
+
|
|
133
|
+
## Constraints
|
|
134
|
+
|
|
135
|
+
- **Dependencies enforced**: Nodes can't run before their inputs are ready
|
|
136
|
+
- **Level structure**: All nodes in a level must complete before next level starts
|
|
137
|
+
- **Concurrency limit**: Won't exceed `maxConcurrency` simultaneous executions
|
|
138
|
+
- **Error handling**: First error in a level stops the flow
|
|
139
|
+
|
|
140
|
+
## For More Details
|
|
141
|
+
|
|
142
|
+
See `/src/flow/PARALLEL_EXECUTION.md` for comprehensive documentation.
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Parallel Execution Refactoring - Summary
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Successfully refactored the `ParallelScheduler` from a Promise-based implementation with undefined `Semaphore` dependencies to a modern Effect-based pattern aligned with the flow engine's architecture.
|
|
6
|
+
|
|
7
|
+
## Changes Made
|
|
8
|
+
|
|
9
|
+
### 1. **ParallelScheduler Refactored** (`src/flow/parallel-scheduler.ts`)
|
|
10
|
+
|
|
11
|
+
#### Issues Fixed
|
|
12
|
+
- ❌ Removed undefined `Semaphore` import and usage
|
|
13
|
+
- ❌ Removed `semaphore()` function call (didn't exist)
|
|
14
|
+
- ✅ Replaced with Effect's native concurrency primitives
|
|
15
|
+
|
|
16
|
+
#### Key Changes
|
|
17
|
+
- Converted `executeNodesInParallel()` from Promise-based to Effect-based
|
|
18
|
+
- Now uses `Effect.all(nodeExecutors, { concurrency: maxConcurrency })`
|
|
19
|
+
- Improved documentation with comprehensive JSDoc comments
|
|
20
|
+
- All methods now properly typed with Effect generics
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
// Before (broken):
|
|
24
|
+
async executeNodesInParallel<T>(
|
|
25
|
+
nodeExecutors: Array<() => Promise<T>>,
|
|
26
|
+
): Promise<T[]> {
|
|
27
|
+
const permit = await this.resourceSemaphore.acquire(); // ERROR: doesn't exist
|
|
28
|
+
// ...
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// After (working):
|
|
32
|
+
executeNodesInParallel<T, E, R>(
|
|
33
|
+
nodeExecutors: Array<() => Effect.Effect<T, E, R>>,
|
|
34
|
+
): Effect.Effect<T[], E, R> {
|
|
35
|
+
return Effect.all(nodeExecutors.map((executor) => executor()), {
|
|
36
|
+
concurrency: this.maxConcurrency,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2. **Flow Engine Integration** (`src/flow/flow.ts`)
|
|
42
|
+
|
|
43
|
+
#### Added Parallel Execution Support
|
|
44
|
+
- Imported `ParallelScheduler`
|
|
45
|
+
- Added parallel execution path in `executeFlow` function
|
|
46
|
+
- Maintains backward compatibility with sequential execution (default)
|
|
47
|
+
|
|
48
|
+
#### Execution Strategy
|
|
49
|
+
```typescript
|
|
50
|
+
if (useParallelExecution) {
|
|
51
|
+
// Parallel execution using execution levels
|
|
52
|
+
const scheduler = new ParallelScheduler({ maxConcurrency: 4 });
|
|
53
|
+
const executionLevels = scheduler.groupNodesByExecutionLevel(nodes, edges);
|
|
54
|
+
|
|
55
|
+
// Execute each level sequentially, but nodes within level in parallel
|
|
56
|
+
for (const level of executionLevels) {
|
|
57
|
+
const levelResults = yield* scheduler.executeNodesInParallel(nodeExecutors);
|
|
58
|
+
// Process results...
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
// Sequential execution (original behavior)
|
|
62
|
+
for (let i = startIndex; i < executionOrder.length; i++) {
|
|
63
|
+
// ...
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
#### Key Features
|
|
69
|
+
- ✅ Level-based execution (respects dependencies)
|
|
70
|
+
- ✅ Configurable concurrency via `maxConcurrency`
|
|
71
|
+
- ✅ Proper pause/resume handling in parallel mode
|
|
72
|
+
- ✅ Debug logging at each level
|
|
73
|
+
- ✅ Backward compatible (sequential is default)
|
|
74
|
+
|
|
75
|
+
### 3. **Documentation Updated** (`src/flow/PARALLEL_EXECUTION.md`)
|
|
76
|
+
|
|
77
|
+
Comprehensive guide covering:
|
|
78
|
+
- ✅ Effect-based implementation details
|
|
79
|
+
- ✅ Configuration options
|
|
80
|
+
- ✅ Usage examples
|
|
81
|
+
- ✅ Performance characteristics
|
|
82
|
+
- ✅ Error handling
|
|
83
|
+
- ✅ Debugging and monitoring
|
|
84
|
+
- ✅ Future enhancements
|
|
85
|
+
|
|
86
|
+
## Configuration
|
|
87
|
+
|
|
88
|
+
Parallel execution is **disabled by default** and can be enabled per flow:
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
const flow = yield* createFlowWithSchema({
|
|
92
|
+
flowId: 'image-pipeline',
|
|
93
|
+
name: 'Image Processing Pipeline',
|
|
94
|
+
nodes: [inputNode, resizeNode, optimizeNode, mergeNode, storageNode],
|
|
95
|
+
edges: [
|
|
96
|
+
{ source: 'input', target: 'resize' },
|
|
97
|
+
{ source: 'input', target: 'optimize' },
|
|
98
|
+
{ source: 'resize', target: 'merge' },
|
|
99
|
+
{ source: 'optimize', target: 'merge' },
|
|
100
|
+
{ source: 'merge', target: 'storage' }
|
|
101
|
+
],
|
|
102
|
+
inputSchema: myInputSchema,
|
|
103
|
+
outputSchema: myOutputSchema,
|
|
104
|
+
parallelExecution: {
|
|
105
|
+
enabled: true,
|
|
106
|
+
maxConcurrency: 4, // Limit concurrent nodes per level
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Architecture
|
|
112
|
+
|
|
113
|
+
### Execution Levels
|
|
114
|
+
Nodes are automatically grouped into levels based on dependencies:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
Level 0: [input] ← No dependencies
|
|
118
|
+
Level 1: [resize, optimize] ← Both depend on input
|
|
119
|
+
Level 2: [merge] ← Depends on resize and optimize
|
|
120
|
+
Level 3: [storage] ← Depends on merge
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Within each level:
|
|
124
|
+
- All nodes execute **in parallel**
|
|
125
|
+
- Concurrency is limited by `maxConcurrency`
|
|
126
|
+
- Uses Effect's `Effect.all()` for safe concurrent execution
|
|
127
|
+
|
|
128
|
+
Between levels:
|
|
129
|
+
- Execution is **strictly sequential**
|
|
130
|
+
- Dependencies are guaranteed to complete before dependents run
|
|
131
|
+
|
|
132
|
+
### Dependency Analysis
|
|
133
|
+
Uses **Kahn's algorithm** for topological sorting:
|
|
134
|
+
1. Build dependency graph from edges
|
|
135
|
+
2. Calculate in-degree for each node
|
|
136
|
+
3. Find nodes with in-degree 0
|
|
137
|
+
4. Group into levels and update in-degrees
|
|
138
|
+
5. Repeat until all nodes are processed
|
|
139
|
+
|
|
140
|
+
## Testing
|
|
141
|
+
|
|
142
|
+
✅ **Build Status**: All 27 packages compile successfully
|
|
143
|
+
- `pnpm build` completes with 0 errors
|
|
144
|
+
- No TypeScript diagnostics
|
|
145
|
+
- ParallelScheduler and flow.ts both type-check cleanly
|
|
146
|
+
|
|
147
|
+
## Backward Compatibility
|
|
148
|
+
|
|
149
|
+
✅ **100% Backward Compatible**
|
|
150
|
+
- Existing flows work without any changes
|
|
151
|
+
- Sequential execution remains the default
|
|
152
|
+
- No breaking changes to public APIs
|
|
153
|
+
- `parallelExecution` is an optional config property
|
|
154
|
+
|
|
155
|
+
## Migration Path
|
|
156
|
+
|
|
157
|
+
1. **Current flows**: Continue working as-is (sequential by default)
|
|
158
|
+
2. **Enable parallel execution**: Add `parallelExecution: { enabled: true }` to existing flows
|
|
159
|
+
3. **Tune performance**: Adjust `maxConcurrency` based on your hardware and workload
|
|
160
|
+
|
|
161
|
+
## Files Modified
|
|
162
|
+
|
|
163
|
+
1. `src/flow/parallel-scheduler.ts` - Complete refactor to Effect-based API
|
|
164
|
+
2. `src/flow/flow.ts` - Integrated parallel scheduler into executeFlow
|
|
165
|
+
3. `src/flow/PARALLEL_EXECUTION.md` - Updated documentation
|
|
166
|
+
4. ✅ All type definitions already in place (`src/flow/types/flow-types.ts`)
|
|
167
|
+
|
|
168
|
+
## Benefits
|
|
169
|
+
|
|
170
|
+
- ✅ **No External Dependencies**: Uses Effect's built-in concurrency
|
|
171
|
+
- ✅ **Type Safe**: Full TypeScript support with proper Effect typing
|
|
172
|
+
- ✅ **Resource Safe**: Concurrency limits prevent resource exhaustion
|
|
173
|
+
- ✅ **Performance**: Independent nodes can run in parallel
|
|
174
|
+
- ✅ **Maintainable**: Clean, well-documented code following Effect patterns
|
|
175
|
+
- ✅ **Debuggable**: Built-in logging and scheduler introspection
|
|
176
|
+
|
|
177
|
+
## Future Enhancements
|
|
178
|
+
|
|
179
|
+
Potential improvements for future releases:
|
|
180
|
+
- Dynamic concurrency adjustment based on resource availability
|
|
181
|
+
- Per-node concurrency limits for resource-intensive operations
|
|
182
|
+
- Parallel level visualization in flow builder UI
|
|
183
|
+
- Performance metrics collection and reporting
|
|
184
|
+
- Adaptive scheduling based on node execution times
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@uploadista/core",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Core package of Uploadista",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Uploadista",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./errors": {
|
|
15
|
+
"types": "./dist/errors/index.d.ts",
|
|
16
|
+
"import": "./dist/errors/index.js",
|
|
17
|
+
"default": "./dist/errors/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./types": {
|
|
20
|
+
"types": "./dist/types/index.d.ts",
|
|
21
|
+
"import": "./dist/types/index.js",
|
|
22
|
+
"default": "./dist/types/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./flow": {
|
|
25
|
+
"types": "./dist/flow/index.d.ts",
|
|
26
|
+
"import": "./dist/flow/index.js",
|
|
27
|
+
"default": "./dist/flow/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./upload": {
|
|
30
|
+
"types": "./dist/upload/index.d.ts",
|
|
31
|
+
"import": "./dist/upload/index.js",
|
|
32
|
+
"default": "./dist/upload/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./logger/*": {
|
|
35
|
+
"types": "./dist/logger/*.d.ts",
|
|
36
|
+
"import": "./dist/logger/*.js",
|
|
37
|
+
"default": "./dist/logger/*.js"
|
|
38
|
+
},
|
|
39
|
+
"./streams/*": {
|
|
40
|
+
"types": "./dist/streams/*.d.ts",
|
|
41
|
+
"import": "./dist/streams/*.js",
|
|
42
|
+
"default": "./dist/streams/*.js"
|
|
43
|
+
},
|
|
44
|
+
"./utils/*": {
|
|
45
|
+
"types": "./dist/utils/*.d.ts",
|
|
46
|
+
"import": "./dist/utils/*.js",
|
|
47
|
+
"default": "./dist/utils/*.js"
|
|
48
|
+
},
|
|
49
|
+
"./websocket": {
|
|
50
|
+
"types": "./dist/websocket/index.d.ts",
|
|
51
|
+
"import": "./dist/websocket/index.js",
|
|
52
|
+
"default": "./dist/websocket/index.js"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"effect": "3.18.4",
|
|
57
|
+
"zod": "4.1.12"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/node": "24.8.1",
|
|
61
|
+
"tsdown": "0.15.7",
|
|
62
|
+
"vitest": "3.2.4",
|
|
63
|
+
"@uploadista/typescript-config": "0.0.2"
|
|
64
|
+
},
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public",
|
|
67
|
+
"registry": "https://registry.npmjs.org/"
|
|
68
|
+
},
|
|
69
|
+
"scripts": {
|
|
70
|
+
"build": "tsc -b",
|
|
71
|
+
"release": "tsdown && bumpp && pnpm publish --access restricted --no-git-checks",
|
|
72
|
+
"format": "biome format --write ./src",
|
|
73
|
+
"lint": "biome lint --write ./src",
|
|
74
|
+
"check": "biome check --write ./src",
|
|
75
|
+
"test": "vitest",
|
|
76
|
+
"test:run": "vitest run",
|
|
77
|
+
"test:watch": "vitest --watch",
|
|
78
|
+
"typecheck": "tsc --noEmit"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ERROR_CATALOG,
|
|
5
|
+
httpFailure,
|
|
6
|
+
isUploadistaError,
|
|
7
|
+
UploadistaError,
|
|
8
|
+
type UploadistaErrorCode,
|
|
9
|
+
} from "../uploadista-error";
|
|
10
|
+
import { Effect } from "effect";
|
|
11
|
+
|
|
12
|
+
describe("ERROR_CATALOG", () => {
|
|
13
|
+
it("should contain all error codes with status and body", () => {
|
|
14
|
+
const errorCodes: UploadistaErrorCode[] = [
|
|
15
|
+
"MISSING_OFFSET",
|
|
16
|
+
"ABORTED",
|
|
17
|
+
"INVALID_TERMINATION",
|
|
18
|
+
"ERR_LOCK_TIMEOUT",
|
|
19
|
+
"INVALID_CONTENT_TYPE",
|
|
20
|
+
"FLOW_STRUCTURE_ERROR",
|
|
21
|
+
"FLOW_CYCLE_ERROR",
|
|
22
|
+
"FLOW_NODE_NOT_FOUND",
|
|
23
|
+
"FLOW_NODE_ERROR",
|
|
24
|
+
"DATASTORE_NOT_FOUND",
|
|
25
|
+
"FILE_NOT_FOUND",
|
|
26
|
+
"INVALID_OFFSET",
|
|
27
|
+
"FILE_NO_LONGER_EXISTS",
|
|
28
|
+
"ERR_SIZE_EXCEEDED",
|
|
29
|
+
"ERR_MAX_SIZE_EXCEEDED",
|
|
30
|
+
"INVALID_LENGTH",
|
|
31
|
+
"INVALID_METADATA",
|
|
32
|
+
"UNKNOWN_ERROR",
|
|
33
|
+
"FILE_WRITE_ERROR",
|
|
34
|
+
"UPLOAD_ID_NOT_FOUND",
|
|
35
|
+
"FLOW_OUTPUT_VALIDATION_ERROR",
|
|
36
|
+
"FLOW_INPUT_VALIDATION_ERROR",
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
for (const code of errorCodes) {
|
|
40
|
+
expect(ERROR_CATALOG[code]).toBeDefined();
|
|
41
|
+
expect(ERROR_CATALOG[code]).toHaveProperty("status");
|
|
42
|
+
expect(ERROR_CATALOG[code]).toHaveProperty("body");
|
|
43
|
+
expect(typeof ERROR_CATALOG[code].status).toBe("number");
|
|
44
|
+
expect(typeof ERROR_CATALOG[code].body).toBe("string");
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should have appropriate HTTP status codes", () => {
|
|
49
|
+
expect(ERROR_CATALOG.MISSING_OFFSET.status).toBe(403);
|
|
50
|
+
expect(ERROR_CATALOG.FILE_NOT_FOUND.status).toBe(404);
|
|
51
|
+
expect(ERROR_CATALOG.INVALID_OFFSET.status).toBe(409);
|
|
52
|
+
expect(ERROR_CATALOG.FILE_NO_LONGER_EXISTS.status).toBe(410);
|
|
53
|
+
expect(ERROR_CATALOG.ERR_SIZE_EXCEEDED.status).toBe(413);
|
|
54
|
+
expect(ERROR_CATALOG.UNKNOWN_ERROR.status).toBe(500);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should have meaningful error messages", () => {
|
|
58
|
+
expect(ERROR_CATALOG.MISSING_OFFSET.body).toContain("Upload-Offset");
|
|
59
|
+
expect(ERROR_CATALOG.FILE_NOT_FOUND.body).toContain("file");
|
|
60
|
+
expect(ERROR_CATALOG.FLOW_CYCLE_ERROR.body).toContain("cycle");
|
|
61
|
+
expect(ERROR_CATALOG.ERR_MAX_SIZE_EXCEEDED.body).toContain("Maximum size");
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("UploadistaError", () => {
|
|
66
|
+
it("should create error with all properties", () => {
|
|
67
|
+
const error = new UploadistaError({
|
|
68
|
+
code: "FILE_NOT_FOUND",
|
|
69
|
+
status: 404,
|
|
70
|
+
body: "File not found",
|
|
71
|
+
cause: new Error("underlying error"),
|
|
72
|
+
details: { fileId: "123" },
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
expect(error.name).toBe("UploadistaError");
|
|
76
|
+
expect(error.code).toBe("FILE_NOT_FOUND");
|
|
77
|
+
expect(error.status).toBe(404);
|
|
78
|
+
expect(error.status_code).toBe(404); // legacy alias
|
|
79
|
+
expect(error.body).toBe("File not found");
|
|
80
|
+
expect(error.message).toBe("File not found");
|
|
81
|
+
expect(error.details).toEqual({ fileId: "123" });
|
|
82
|
+
expect((error as { cause?: unknown }).cause).toBeInstanceOf(Error);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should create error without optional properties", () => {
|
|
86
|
+
const error = new UploadistaError({
|
|
87
|
+
code: "UNKNOWN_ERROR",
|
|
88
|
+
status: 500,
|
|
89
|
+
body: "Something went wrong",
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
expect(error.code).toBe("UNKNOWN_ERROR");
|
|
93
|
+
expect(error.status).toBe(500);
|
|
94
|
+
expect(error.body).toBe("Something went wrong");
|
|
95
|
+
expect(error.details).toBeUndefined();
|
|
96
|
+
expect((error as { cause?: unknown }).cause).toBeUndefined();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("should inherit from Error", () => {
|
|
100
|
+
const error = new UploadistaError({
|
|
101
|
+
code: "FILE_NOT_FOUND",
|
|
102
|
+
status: 404,
|
|
103
|
+
body: "File not found",
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(error).toBeInstanceOf(Error);
|
|
107
|
+
expect(error).toBeInstanceOf(UploadistaError);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe("fromCode", () => {
|
|
111
|
+
it("should create error from error code", () => {
|
|
112
|
+
const error = UploadistaError.fromCode("FILE_NOT_FOUND");
|
|
113
|
+
|
|
114
|
+
expect(error.code).toBe("FILE_NOT_FOUND");
|
|
115
|
+
expect(error.status).toBe(ERROR_CATALOG.FILE_NOT_FOUND.status);
|
|
116
|
+
expect(error.body).toBe(ERROR_CATALOG.FILE_NOT_FOUND.body);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should allow overriding status and body", () => {
|
|
120
|
+
const error = UploadistaError.fromCode("FILE_NOT_FOUND", {
|
|
121
|
+
status: 400,
|
|
122
|
+
body: "Custom message",
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
expect(error.code).toBe("FILE_NOT_FOUND");
|
|
126
|
+
expect(error.status).toBe(400);
|
|
127
|
+
expect(error.body).toBe("Custom message");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("should include details and cause when provided", () => {
|
|
131
|
+
const cause = new Error("root cause");
|
|
132
|
+
const error = UploadistaError.fromCode("UNKNOWN_ERROR", {
|
|
133
|
+
details: { extra: "info" },
|
|
134
|
+
cause: cause,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expect(error.details).toEqual({ extra: "info" });
|
|
138
|
+
expect((error as { cause?: unknown }).cause).toBe(cause);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("should work with all error codes", () => {
|
|
142
|
+
const errorCodes: UploadistaErrorCode[] = [
|
|
143
|
+
"MISSING_OFFSET",
|
|
144
|
+
"ABORTED",
|
|
145
|
+
"INVALID_TERMINATION",
|
|
146
|
+
"ERR_LOCK_TIMEOUT",
|
|
147
|
+
"INVALID_CONTENT_TYPE",
|
|
148
|
+
"FLOW_STRUCTURE_ERROR",
|
|
149
|
+
"FLOW_CYCLE_ERROR",
|
|
150
|
+
"FLOW_NODE_NOT_FOUND",
|
|
151
|
+
"FLOW_NODE_ERROR",
|
|
152
|
+
"DATASTORE_NOT_FOUND",
|
|
153
|
+
"FILE_NOT_FOUND",
|
|
154
|
+
"INVALID_OFFSET",
|
|
155
|
+
"FILE_NO_LONGER_EXISTS",
|
|
156
|
+
"ERR_SIZE_EXCEEDED",
|
|
157
|
+
"ERR_MAX_SIZE_EXCEEDED",
|
|
158
|
+
"INVALID_LENGTH",
|
|
159
|
+
"INVALID_METADATA",
|
|
160
|
+
"UNKNOWN_ERROR",
|
|
161
|
+
"FILE_WRITE_ERROR",
|
|
162
|
+
"UPLOAD_ID_NOT_FOUND",
|
|
163
|
+
"FLOW_OUTPUT_VALIDATION_ERROR",
|
|
164
|
+
"FLOW_INPUT_VALIDATION_ERROR",
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
for (const code of errorCodes) {
|
|
168
|
+
const error = UploadistaError.fromCode(code);
|
|
169
|
+
expect(error.code).toBe(code);
|
|
170
|
+
expect(error.status).toBe(ERROR_CATALOG[code].status);
|
|
171
|
+
expect(error.body).toBe(ERROR_CATALOG[code].body);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe("toFailure", () => {
|
|
177
|
+
it("should convert error to failure result", () => {
|
|
178
|
+
const error = UploadistaError.fromCode("FILE_NOT_FOUND");
|
|
179
|
+
const result = error.toEffect();
|
|
180
|
+
|
|
181
|
+
expect(Effect.isFailure(result)).toBe(true);
|
|
182
|
+
expect(Effect.isSuccess(result)).toBe(false);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe("isUploadistaError", () => {
|
|
188
|
+
it("should return true for UploadistaError instances", () => {
|
|
189
|
+
const error = UploadistaError.fromCode("FILE_NOT_FOUND");
|
|
190
|
+
expect(isUploadistaError(error)).toBe(true);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("should return false for regular Error instances", () => {
|
|
194
|
+
const error = new Error("regular error");
|
|
195
|
+
expect(isUploadistaError(error)).toBe(false);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("should return false for non-error values", () => {
|
|
199
|
+
expect(isUploadistaError("string")).toBe(false);
|
|
200
|
+
expect(isUploadistaError(null)).toBe(false);
|
|
201
|
+
expect(isUploadistaError(undefined)).toBe(false);
|
|
202
|
+
expect(isUploadistaError({})).toBe(false);
|
|
203
|
+
expect(isUploadistaError(123)).toBe(false);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("should work as type guard", () => {
|
|
207
|
+
const error: unknown = UploadistaError.fromCode("FILE_NOT_FOUND");
|
|
208
|
+
|
|
209
|
+
if (isUploadistaError(error)) {
|
|
210
|
+
// TypeScript should know error is UploadistaError
|
|
211
|
+
expect(error.code).toBe("FILE_NOT_FOUND");
|
|
212
|
+
expect(error.status).toBe(404);
|
|
213
|
+
} else {
|
|
214
|
+
expect.fail("Should be UploadistaError");
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
describe("httpFailure", () => {
|
|
220
|
+
it("should create failure result from error code", () => {
|
|
221
|
+
const result = httpFailure("FILE_NOT_FOUND");
|
|
222
|
+
|
|
223
|
+
expect(Effect.isFailure(result)).toBe(true);
|
|
224
|
+
expect(Effect.isSuccess(result)).toBe(false);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("should allow overriding error properties", () => {
|
|
228
|
+
const result = httpFailure("UNKNOWN_ERROR", {
|
|
229
|
+
status: 503,
|
|
230
|
+
body: "Service unavailable",
|
|
231
|
+
details: { retryAfter: 60 },
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
expect(Effect.isFailure(result)).toBe(true);
|
|
235
|
+
expect(Effect.isSuccess(result)).toBe(false);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("should work with all error codes", () => {
|
|
239
|
+
const errorCodes: UploadistaErrorCode[] = [
|
|
240
|
+
"MISSING_OFFSET",
|
|
241
|
+
"FLOW_NODE_ERROR",
|
|
242
|
+
"INVALID_METADATA",
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
for (const code of errorCodes) {
|
|
246
|
+
const result = httpFailure(code);
|
|
247
|
+
expect(Effect.isFailure(result)).toBe(true);
|
|
248
|
+
expect(Effect.isSuccess(result)).toBe(false);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
});
|