@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
package/README.md
ADDED
|
@@ -0,0 +1,1120 @@
|
|
|
1
|
+
# @uploadista/core
|
|
2
|
+
|
|
3
|
+
Core engine for the Uploadista platform, providing a powerful and flexible system for file uploads and processing pipelines built with Effect-TS.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`@uploadista/core` is the foundation of the Uploadista ecosystem, providing:
|
|
8
|
+
|
|
9
|
+
- **Flow Engine**: A DAG-based processing pipeline for transforming files through multiple processing steps
|
|
10
|
+
- **Upload System**: Robust file upload handling with chunked, resumable, and parallel upload strategies
|
|
11
|
+
- **Stream Utilities**: Advanced stream manipulation tools for splitting, combining, and limiting data streams
|
|
12
|
+
- **Error Handling**: Comprehensive error types and error management with Effect-TS integration
|
|
13
|
+
- **Type Safety**: Full TypeScript support with Zod schema validation throughout
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @uploadista/core
|
|
19
|
+
# or
|
|
20
|
+
pnpm add @uploadista/core
|
|
21
|
+
# or
|
|
22
|
+
yarn add @uploadista/core
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Core Dependencies
|
|
26
|
+
|
|
27
|
+
- **effect**: For functional effect system and dependency injection
|
|
28
|
+
- **zod**: For runtime type validation and schema definition
|
|
29
|
+
- **@uploadista/observability**: For tracing and monitoring
|
|
30
|
+
|
|
31
|
+
## Architecture Overview
|
|
32
|
+
|
|
33
|
+
### Effect-TS First
|
|
34
|
+
|
|
35
|
+
`@uploadista/core` is built on Effect-TS, providing:
|
|
36
|
+
|
|
37
|
+
- **Type-safe effects**: All operations return Effect types for composable, type-safe error handling
|
|
38
|
+
- **Dependency injection**: Uses Effect's Context system for managing dependencies
|
|
39
|
+
- **Resource management**: Automatic cleanup and resource disposal
|
|
40
|
+
- **Observability**: Built-in tracing and logging support
|
|
41
|
+
|
|
42
|
+
### Core Components
|
|
43
|
+
|
|
44
|
+
1. **Flow Engine** (`/flow`): DAG-based processing pipeline with nodes and edges
|
|
45
|
+
2. **Upload System** (`/upload`): File upload handling with multiple strategies
|
|
46
|
+
3. **Streams** (`/streams`): Stream manipulation utilities for data processing
|
|
47
|
+
4. **Types** (`/types`): Core type definitions and interfaces
|
|
48
|
+
5. **Errors** (`/errors`): Comprehensive error handling system
|
|
49
|
+
6. **Utils** (`/utils`): Utility functions for common operations
|
|
50
|
+
7. **Logger** (`/logger`): Simple logging utilities
|
|
51
|
+
8. **WebSocket** (`/websocket`): Real-time event streaming
|
|
52
|
+
|
|
53
|
+
## Module Exports
|
|
54
|
+
|
|
55
|
+
### Main Entry Point (`.`)
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import {
|
|
59
|
+
// Flow Engine
|
|
60
|
+
createFlow,
|
|
61
|
+
createFlowNode,
|
|
62
|
+
FlowServer,
|
|
63
|
+
|
|
64
|
+
// Upload System
|
|
65
|
+
UploadServer,
|
|
66
|
+
createUploadServer,
|
|
67
|
+
|
|
68
|
+
// Error Handling
|
|
69
|
+
UploadistaError,
|
|
70
|
+
ERRORS,
|
|
71
|
+
|
|
72
|
+
// Types
|
|
73
|
+
type Flow,
|
|
74
|
+
type FlowNode,
|
|
75
|
+
type UploadFile,
|
|
76
|
+
type DataStore,
|
|
77
|
+
type KvStore,
|
|
78
|
+
} from "@uploadista/core";
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Errors Module (`/errors`)
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { UploadistaError, ERRORS } from "@uploadista/core/errors";
|
|
85
|
+
|
|
86
|
+
// Create error from error code
|
|
87
|
+
const error = UploadistaError.fromCode("FLOW_NODE_NOT_FOUND");
|
|
88
|
+
|
|
89
|
+
// Create error with overrides
|
|
90
|
+
const customError = UploadistaError.fromCode("FLOW_NODE_ERROR", {
|
|
91
|
+
body: "Custom error message",
|
|
92
|
+
cause: originalError,
|
|
93
|
+
details: { nodeId: "abc123" }
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Convert to Effect
|
|
97
|
+
const errorEffect = error.toEffect<void>();
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Available Error Codes**:
|
|
101
|
+
- Upload errors: `MISSING_OFFSET`, `INVALID_OFFSET`, `ERR_SIZE_EXCEEDED`, `INVALID_LENGTH`
|
|
102
|
+
- Flow errors: `FLOW_NODE_NOT_FOUND`, `FLOW_NODE_ERROR`, `FLOW_CYCLE_ERROR`, `FLOW_JOB_NOT_FOUND`
|
|
103
|
+
- Storage errors: `DATASTORE_NOT_FOUND`, `FILE_NOT_FOUND`, `STORAGE_NOT_AUTHORIZED`
|
|
104
|
+
- Validation errors: `VALIDATION_ERROR`, `FLOW_INPUT_VALIDATION_ERROR`, `FLOW_OUTPUT_VALIDATION_ERROR`
|
|
105
|
+
|
|
106
|
+
### Types Module (`/types`)
|
|
107
|
+
|
|
108
|
+
Core type definitions for the system:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import type {
|
|
112
|
+
// Storage
|
|
113
|
+
DataStore,
|
|
114
|
+
DataStoreCapabilities,
|
|
115
|
+
DataStoreWriteOptions,
|
|
116
|
+
|
|
117
|
+
// KV Store
|
|
118
|
+
KvStore,
|
|
119
|
+
BaseKvStore,
|
|
120
|
+
|
|
121
|
+
// Upload
|
|
122
|
+
UploadFile,
|
|
123
|
+
InputFile,
|
|
124
|
+
UploadEvent,
|
|
125
|
+
|
|
126
|
+
// Events
|
|
127
|
+
EventEmitter,
|
|
128
|
+
EventBroadcaster,
|
|
129
|
+
|
|
130
|
+
// WebSocket
|
|
131
|
+
WebSocketConnection,
|
|
132
|
+
WebSocketMessage,
|
|
133
|
+
|
|
134
|
+
// Middleware
|
|
135
|
+
Middleware,
|
|
136
|
+
} from "@uploadista/core/types";
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Flow Module (`/flow`)
|
|
140
|
+
|
|
141
|
+
The Flow Engine provides a powerful DAG-based processing system:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import {
|
|
145
|
+
createFlow,
|
|
146
|
+
createFlowNode,
|
|
147
|
+
createFlowEdge,
|
|
148
|
+
FlowServer,
|
|
149
|
+
NodeType,
|
|
150
|
+
EventType,
|
|
151
|
+
type Flow,
|
|
152
|
+
type FlowNode,
|
|
153
|
+
type FlowEdge,
|
|
154
|
+
type FlowJob,
|
|
155
|
+
} from "@uploadista/core/flow";
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### Creating a Flow
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { createFlow, createFlowNode, NodeType } from "@uploadista/core/flow";
|
|
162
|
+
import { z } from "zod";
|
|
163
|
+
import { Effect } from "effect";
|
|
164
|
+
|
|
165
|
+
// Define schemas
|
|
166
|
+
const inputSchema = z.object({
|
|
167
|
+
id: z.string(),
|
|
168
|
+
stream: z.instanceof(Uint8Array),
|
|
169
|
+
metadata: z.record(z.unknown()),
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const outputSchema = z.object({
|
|
173
|
+
id: z.string(),
|
|
174
|
+
url: z.string(),
|
|
175
|
+
size: z.number(),
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Create nodes
|
|
179
|
+
const inputNode = createFlowNode({
|
|
180
|
+
id: "input-1",
|
|
181
|
+
name: "File Input",
|
|
182
|
+
description: "Accepts incoming files",
|
|
183
|
+
type: NodeType.input,
|
|
184
|
+
inputSchema,
|
|
185
|
+
outputSchema: inputSchema,
|
|
186
|
+
run: ({ data }) => Effect.succeed({ type: "complete", data }),
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const processNode = createFlowNode({
|
|
190
|
+
id: "process-1",
|
|
191
|
+
name: "Process File",
|
|
192
|
+
description: "Processes the file",
|
|
193
|
+
type: NodeType.process,
|
|
194
|
+
inputSchema,
|
|
195
|
+
outputSchema,
|
|
196
|
+
run: ({ data, storageId, jobId }) =>
|
|
197
|
+
Effect.gen(function* () {
|
|
198
|
+
// Processing logic here
|
|
199
|
+
const result = {
|
|
200
|
+
id: data.id,
|
|
201
|
+
url: `https://storage.example.com/${data.id}`,
|
|
202
|
+
size: data.stream.byteLength,
|
|
203
|
+
};
|
|
204
|
+
return { type: "complete", data: result };
|
|
205
|
+
}),
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Create flow
|
|
209
|
+
const flow = yield* createFlow({
|
|
210
|
+
flowId: "my-flow",
|
|
211
|
+
name: "My Processing Flow",
|
|
212
|
+
inputSchema,
|
|
213
|
+
outputSchema,
|
|
214
|
+
nodes: [inputNode, processNode],
|
|
215
|
+
edges: [
|
|
216
|
+
{ source: "input-1", target: "process-1" }
|
|
217
|
+
],
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Run the flow
|
|
221
|
+
const result = yield* flow.run({
|
|
222
|
+
inputs: {
|
|
223
|
+
"input-1": { id: "file-1", stream: new Uint8Array([...]), metadata: {} }
|
|
224
|
+
},
|
|
225
|
+
storageId: "storage-1",
|
|
226
|
+
jobId: "job-1",
|
|
227
|
+
clientId: "client-1",
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
#### Flow Execution Results
|
|
232
|
+
|
|
233
|
+
Flows can complete or pause (for nodes that wait for additional data):
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
type FlowExecutionResult<TOutput> =
|
|
237
|
+
| { type: "completed"; result: TOutput }
|
|
238
|
+
| {
|
|
239
|
+
type: "paused";
|
|
240
|
+
nodeId: string;
|
|
241
|
+
executionState: {
|
|
242
|
+
executionOrder: string[];
|
|
243
|
+
currentIndex: number;
|
|
244
|
+
inputs: Record<string, unknown>;
|
|
245
|
+
};
|
|
246
|
+
};
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
#### Node Types
|
|
250
|
+
|
|
251
|
+
- **input**: Entry point for data into the flow
|
|
252
|
+
- **process**: Transforms data
|
|
253
|
+
- **output**: Saves data to storage
|
|
254
|
+
- **conditional**: Routes data based on conditions
|
|
255
|
+
- **multiplex**: Splits data to multiple outputs
|
|
256
|
+
- **merge**: Combines multiple inputs
|
|
257
|
+
|
|
258
|
+
#### Node Features
|
|
259
|
+
|
|
260
|
+
**Conditional Execution**:
|
|
261
|
+
```typescript
|
|
262
|
+
createFlowNode({
|
|
263
|
+
// ... other config
|
|
264
|
+
condition: {
|
|
265
|
+
field: "mimeType",
|
|
266
|
+
operator: "equals",
|
|
267
|
+
value: "image/jpeg"
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**Multi-Input Nodes**:
|
|
273
|
+
```typescript
|
|
274
|
+
createFlowNode({
|
|
275
|
+
// ... other config
|
|
276
|
+
multiInput: true,
|
|
277
|
+
run: ({ inputs }) => {
|
|
278
|
+
// inputs is Record<string, unknown>
|
|
279
|
+
// Process all inputs together
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
**Retry Configuration**:
|
|
285
|
+
```typescript
|
|
286
|
+
createFlowNode({
|
|
287
|
+
// ... other config
|
|
288
|
+
retry: {
|
|
289
|
+
maxRetries: 3,
|
|
290
|
+
retryDelay: 1000,
|
|
291
|
+
exponentialBackoff: true
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**Pausable Nodes**:
|
|
297
|
+
```typescript
|
|
298
|
+
createFlowNode({
|
|
299
|
+
// ... other config
|
|
300
|
+
pausable: true,
|
|
301
|
+
run: ({ data }) => {
|
|
302
|
+
if (needsMoreData) {
|
|
303
|
+
return Effect.succeed({
|
|
304
|
+
type: "waiting",
|
|
305
|
+
partialData: data
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
return Effect.succeed({
|
|
309
|
+
type: "complete",
|
|
310
|
+
data: processedData
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
#### Flow Events
|
|
317
|
+
|
|
318
|
+
Monitor flow execution with event callbacks:
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
createFlow({
|
|
322
|
+
// ... other config
|
|
323
|
+
onEvent: (event) => Effect.gen(function* () {
|
|
324
|
+
switch (event.eventType) {
|
|
325
|
+
case EventType.FlowStart:
|
|
326
|
+
console.log("Flow started", event.flowId);
|
|
327
|
+
break;
|
|
328
|
+
case EventType.NodeStart:
|
|
329
|
+
console.log("Node started", event.nodeId);
|
|
330
|
+
break;
|
|
331
|
+
case EventType.NodeEnd:
|
|
332
|
+
console.log("Node completed", event.nodeId, event.result);
|
|
333
|
+
break;
|
|
334
|
+
case EventType.NodeError:
|
|
335
|
+
console.error("Node failed", event.nodeId, event.error);
|
|
336
|
+
break;
|
|
337
|
+
case EventType.FlowEnd:
|
|
338
|
+
console.log("Flow completed", event.result);
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
})
|
|
342
|
+
});
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
#### FlowServer
|
|
346
|
+
|
|
347
|
+
The FlowServer provides a high-level interface for managing flow execution:
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
import { FlowServer, createFlowServer } from "@uploadista/core/flow";
|
|
351
|
+
import { Effect, Context, Layer } from "effect";
|
|
352
|
+
|
|
353
|
+
// Create a flow provider
|
|
354
|
+
const flowProvider = Layer.succeed(FlowProvider, {
|
|
355
|
+
getFlow: (flowId, clientId) => Effect.succeed(myFlow)
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// Build the FlowServer with dependencies
|
|
359
|
+
const program = Effect.gen(function* () {
|
|
360
|
+
const flowServer = yield* FlowServer;
|
|
361
|
+
|
|
362
|
+
// Run a flow
|
|
363
|
+
const job = yield* flowServer.runFlow({
|
|
364
|
+
flowId: "my-flow",
|
|
365
|
+
storageId: "storage-1",
|
|
366
|
+
clientId: "client-1",
|
|
367
|
+
inputs: { "input-1": fileData }
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// Monitor job status
|
|
371
|
+
const status = yield* flowServer.getJobStatus(job.id);
|
|
372
|
+
|
|
373
|
+
// Continue a paused flow
|
|
374
|
+
if (status.status === "paused" && status.pausedAt) {
|
|
375
|
+
yield* flowServer.continueFlow({
|
|
376
|
+
jobId: job.id,
|
|
377
|
+
nodeId: status.pausedAt,
|
|
378
|
+
newData: additionalData,
|
|
379
|
+
clientId: "client-1"
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// Run with dependencies
|
|
385
|
+
Effect.runPromise(
|
|
386
|
+
program.pipe(
|
|
387
|
+
Effect.provide(flowProvider),
|
|
388
|
+
Effect.provide(uploadFileKvStore),
|
|
389
|
+
Effect.provide(flowJobKvStore),
|
|
390
|
+
Effect.provide(eventEmitter)
|
|
391
|
+
)
|
|
392
|
+
);
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Upload Module (`/upload`)
|
|
396
|
+
|
|
397
|
+
Handles file uploads with multiple strategies:
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
import {
|
|
401
|
+
UploadServer,
|
|
402
|
+
createUploadServer,
|
|
403
|
+
uploadServer,
|
|
404
|
+
type UploadServerShape,
|
|
405
|
+
} from "@uploadista/core/upload";
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
#### UploadServer
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
import { UploadServer } from "@uploadista/core/upload";
|
|
412
|
+
import { Effect } from "effect";
|
|
413
|
+
|
|
414
|
+
const program = Effect.gen(function* () {
|
|
415
|
+
const uploadServer = yield* UploadServer;
|
|
416
|
+
|
|
417
|
+
// Create an upload
|
|
418
|
+
const upload = yield* uploadServer.createUpload(
|
|
419
|
+
{
|
|
420
|
+
filename: "image.jpg",
|
|
421
|
+
size: 1024000,
|
|
422
|
+
mimeType: "image/jpeg",
|
|
423
|
+
storageId: "storage-1",
|
|
424
|
+
metadata: { description: "Profile photo" }
|
|
425
|
+
},
|
|
426
|
+
"client-1"
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
// Upload chunks
|
|
430
|
+
const completed = yield* uploadServer.uploadChunk(
|
|
431
|
+
upload.id,
|
|
432
|
+
"client-1",
|
|
433
|
+
readableStream
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
// Upload directly with stream
|
|
437
|
+
const file = yield* uploadServer.upload(
|
|
438
|
+
inputFile,
|
|
439
|
+
"client-1",
|
|
440
|
+
readableStream
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
// Upload from URL
|
|
444
|
+
const fromUrl = yield* uploadServer.uploadFromUrl(
|
|
445
|
+
inputFile,
|
|
446
|
+
"client-1",
|
|
447
|
+
"https://example.com/image.jpg"
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
// Read file data
|
|
451
|
+
const data = yield* uploadServer.read(upload.id, "client-1");
|
|
452
|
+
|
|
453
|
+
// Delete file
|
|
454
|
+
yield* uploadServer.delete(upload.id, "client-1");
|
|
455
|
+
|
|
456
|
+
// Get storage capabilities
|
|
457
|
+
const capabilities = yield* uploadServer.getCapabilities(
|
|
458
|
+
"storage-1",
|
|
459
|
+
"client-1"
|
|
460
|
+
);
|
|
461
|
+
});
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
#### Upload Strategies
|
|
465
|
+
|
|
466
|
+
DataStores can support different upload strategies:
|
|
467
|
+
|
|
468
|
+
- **Single**: Upload file in one request
|
|
469
|
+
- **Parallel**: Upload chunks in parallel (S3, GCS, Azure)
|
|
470
|
+
- **Resumable**: Resume interrupted uploads
|
|
471
|
+
- **Transactional**: Atomic commit after all chunks
|
|
472
|
+
|
|
473
|
+
Check capabilities:
|
|
474
|
+
```typescript
|
|
475
|
+
const capabilities = yield* uploadServer.getCapabilities(storageId, clientId);
|
|
476
|
+
|
|
477
|
+
if (capabilities.supportsParallelUploads) {
|
|
478
|
+
// Use parallel upload strategy
|
|
479
|
+
console.log("Max parts:", capabilities.maxParts);
|
|
480
|
+
console.log("Optimal chunk size:", capabilities.optimalChunkSize);
|
|
481
|
+
}
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### Streams Module (`/streams/*`)
|
|
485
|
+
|
|
486
|
+
Advanced stream manipulation utilities:
|
|
487
|
+
|
|
488
|
+
#### StreamLimiter
|
|
489
|
+
|
|
490
|
+
Limit stream data rate or total size:
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
import { StreamLimiter } from "@uploadista/core/streams/stream-limiter";
|
|
494
|
+
|
|
495
|
+
const limiter = new StreamLimiter({
|
|
496
|
+
maxSize: 100 * 1024 * 1024, // 100MB max
|
|
497
|
+
onProgress: (bytesRead) => {
|
|
498
|
+
console.log(`Progress: ${bytesRead} bytes`);
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
const limited = inputStream.pipeThrough(limiter.transform);
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### Utils Module (`/utils/*`)
|
|
506
|
+
|
|
507
|
+
Utility functions for common operations:
|
|
508
|
+
|
|
509
|
+
#### Debounce
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
import { debounce } from "@uploadista/core/utils/debounce";
|
|
513
|
+
|
|
514
|
+
const debouncedFn = debounce(
|
|
515
|
+
(value: string) => console.log("Search:", value),
|
|
516
|
+
300,
|
|
517
|
+
{ leading: false, trailing: true }
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
// Only logs once after 300ms of no calls
|
|
521
|
+
debouncedFn("a");
|
|
522
|
+
debouncedFn("ab");
|
|
523
|
+
debouncedFn("abc"); // Logs "Search: abc" after 300ms
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
#### Throttle
|
|
527
|
+
|
|
528
|
+
```typescript
|
|
529
|
+
import { throttle } from "@uploadista/core/utils/throttle";
|
|
530
|
+
|
|
531
|
+
const throttledFn = throttle(
|
|
532
|
+
(value: number) => console.log("Value:", value),
|
|
533
|
+
1000
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
// Logs immediately, then at most once per second
|
|
537
|
+
throttledFn(1); // Logs immediately
|
|
538
|
+
throttledFn(2); // Ignored (within 1s)
|
|
539
|
+
throttledFn(3); // Ignored (within 1s)
|
|
540
|
+
// After 1s
|
|
541
|
+
throttledFn(4); // Logs "Value: 4"
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
#### Once
|
|
545
|
+
|
|
546
|
+
Ensure a function runs only once:
|
|
547
|
+
|
|
548
|
+
```typescript
|
|
549
|
+
import { once } from "@uploadista/core/utils/once";
|
|
550
|
+
|
|
551
|
+
const initialize = once(() => {
|
|
552
|
+
console.log("Initializing...");
|
|
553
|
+
// Expensive setup
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
initialize(); // Logs "Initializing..."
|
|
557
|
+
initialize(); // Does nothing
|
|
558
|
+
initialize(); // Does nothing
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
#### Generate ID
|
|
562
|
+
|
|
563
|
+
Generate unique identifiers:
|
|
564
|
+
|
|
565
|
+
```typescript
|
|
566
|
+
import { GenerateId } from "@uploadista/core/utils/generate-id";
|
|
567
|
+
import { Effect } from "effect";
|
|
568
|
+
|
|
569
|
+
const program = Effect.gen(function* () {
|
|
570
|
+
const generateId = yield* GenerateId;
|
|
571
|
+
const id = yield* generateId.generate();
|
|
572
|
+
console.log("ID:", id); // e.g., "abc123def456"
|
|
573
|
+
});
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### Logger Module (`/logger/*`)
|
|
577
|
+
|
|
578
|
+
Simple logging utilities:
|
|
579
|
+
|
|
580
|
+
```typescript
|
|
581
|
+
import { createLogger } from "@uploadista/core/logger/logger";
|
|
582
|
+
|
|
583
|
+
const logger = createLogger(true); // enabled
|
|
584
|
+
logger.log("Processing file..."); // Logs to console
|
|
585
|
+
|
|
586
|
+
const disabledLogger = createLogger(false); // disabled
|
|
587
|
+
disabledLogger.log("This won't log"); // Silent
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### WebSocket Module (`/websocket`)
|
|
591
|
+
|
|
592
|
+
Real-time event streaming:
|
|
593
|
+
|
|
594
|
+
```typescript
|
|
595
|
+
import type {
|
|
596
|
+
WebSocketConnection,
|
|
597
|
+
WebSocketMessage,
|
|
598
|
+
} from "@uploadista/core/websocket";
|
|
599
|
+
|
|
600
|
+
// Platform-agnostic WebSocket interface
|
|
601
|
+
const connection: WebSocketConnection = {
|
|
602
|
+
id: "conn-123",
|
|
603
|
+
readyState: 1, // OPEN
|
|
604
|
+
send: (data: string) => ws.send(data),
|
|
605
|
+
close: (code?: number, reason?: string) => ws.close(code, reason),
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
// Subscribe to events
|
|
609
|
+
yield* uploadServer.subscribeToUploadEvents(uploadId, connection);
|
|
610
|
+
yield* flowServer.subscribeToFlowEvents(jobId, connection);
|
|
611
|
+
|
|
612
|
+
// Message types
|
|
613
|
+
type WebSocketMessage =
|
|
614
|
+
| { type: "upload_event"; payload: UploadEvent }
|
|
615
|
+
| { type: "flow_event"; payload: FlowEvent }
|
|
616
|
+
| { type: "subscribed"; payload: { eventKey: string } }
|
|
617
|
+
| { type: "error"; message: string }
|
|
618
|
+
| { type: "ping" | "pong"; timestamp?: string };
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
## Plugin System
|
|
622
|
+
|
|
623
|
+
`@uploadista/core` provides plugin interfaces for extending functionality:
|
|
624
|
+
|
|
625
|
+
### Image Plugin
|
|
626
|
+
|
|
627
|
+
Image processing operations (resize, optimize):
|
|
628
|
+
|
|
629
|
+
```typescript
|
|
630
|
+
import { ImagePlugin } from "@uploadista/core/flow";
|
|
631
|
+
import { Effect, Layer } from "effect";
|
|
632
|
+
|
|
633
|
+
// Implement the plugin
|
|
634
|
+
const imagePlugin = Layer.succeed(ImagePlugin, {
|
|
635
|
+
resize: (input, options) => Effect.gen(function* () {
|
|
636
|
+
// Resize implementation
|
|
637
|
+
return resizedImage;
|
|
638
|
+
}),
|
|
639
|
+
optimize: (input, options) => Effect.gen(function* () {
|
|
640
|
+
// Optimize implementation
|
|
641
|
+
return optimizedImage;
|
|
642
|
+
})
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
// Use in flows
|
|
646
|
+
const resizeNode = createFlowNode({
|
|
647
|
+
// ... config
|
|
648
|
+
run: ({ data }) => Effect.gen(function* () {
|
|
649
|
+
const plugin = yield* ImagePlugin;
|
|
650
|
+
const resized = yield* plugin.resize(data.stream, {
|
|
651
|
+
width: 800,
|
|
652
|
+
height: 600,
|
|
653
|
+
fit: "cover"
|
|
654
|
+
});
|
|
655
|
+
return { type: "complete", data: { ...data, stream: resized } };
|
|
656
|
+
})
|
|
657
|
+
});
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
### Image AI Plugin
|
|
661
|
+
|
|
662
|
+
AI-powered image operations:
|
|
663
|
+
|
|
664
|
+
```typescript
|
|
665
|
+
import { ImageAIPlugin } from "@uploadista/core/flow";
|
|
666
|
+
|
|
667
|
+
// Implement the plugin
|
|
668
|
+
const imageAIPlugin = Layer.succeed(ImageAIPlugin, {
|
|
669
|
+
describeImage: (input, options) => Effect.gen(function* () {
|
|
670
|
+
// AI description implementation
|
|
671
|
+
return "A beautiful sunset over mountains";
|
|
672
|
+
}),
|
|
673
|
+
removeBackground: (input, options) => Effect.gen(function* () {
|
|
674
|
+
// Background removal implementation
|
|
675
|
+
return imageWithoutBackground;
|
|
676
|
+
})
|
|
677
|
+
});
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
### Zip Plugin
|
|
681
|
+
|
|
682
|
+
Create ZIP archives from multiple files:
|
|
683
|
+
|
|
684
|
+
```typescript
|
|
685
|
+
import { ZipPlugin } from "@uploadista/core/flow";
|
|
686
|
+
|
|
687
|
+
const zipPlugin = Layer.succeed(ZipPlugin, {
|
|
688
|
+
zip: (inputs, options) => Effect.gen(function* () {
|
|
689
|
+
// ZIP creation implementation
|
|
690
|
+
return zipFileData;
|
|
691
|
+
})
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
// Use in a merge node
|
|
695
|
+
const zipNode = createFlowNode({
|
|
696
|
+
id: "zip-1",
|
|
697
|
+
type: NodeType.merge,
|
|
698
|
+
multiInput: true,
|
|
699
|
+
run: ({ inputs }) => Effect.gen(function* () {
|
|
700
|
+
const plugin = yield* ZipPlugin;
|
|
701
|
+
|
|
702
|
+
const zipInputs = Object.entries(inputs).map(([id, data]) => ({
|
|
703
|
+
id,
|
|
704
|
+
data: data.stream,
|
|
705
|
+
metadata: data.metadata
|
|
706
|
+
}));
|
|
707
|
+
|
|
708
|
+
const zipData = yield* plugin.zip(zipInputs, {
|
|
709
|
+
zipName: "archive.zip",
|
|
710
|
+
includeMetadata: true
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
return {
|
|
714
|
+
type: "complete",
|
|
715
|
+
data: { stream: zipData, filename: "archive.zip" }
|
|
716
|
+
};
|
|
717
|
+
})
|
|
718
|
+
});
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
### Credential Provider
|
|
722
|
+
|
|
723
|
+
Secure credential management for plugins:
|
|
724
|
+
|
|
725
|
+
```typescript
|
|
726
|
+
import { CredentialProvider } from "@uploadista/core/flow";
|
|
727
|
+
|
|
728
|
+
const credentialProvider = Layer.succeed(CredentialProvider, {
|
|
729
|
+
getCredential: (credentialId, clientId) => Effect.gen(function* () {
|
|
730
|
+
// Fetch credential securely
|
|
731
|
+
return {
|
|
732
|
+
id: credentialId,
|
|
733
|
+
value: "api-key-value",
|
|
734
|
+
metadata: {}
|
|
735
|
+
};
|
|
736
|
+
})
|
|
737
|
+
});
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
## Data Stores
|
|
741
|
+
|
|
742
|
+
`@uploadista/core` defines the `DataStore` interface for storage backends. Implementations are provided in separate packages:
|
|
743
|
+
|
|
744
|
+
- `@uploadista/data-stores-s3` - AWS S3
|
|
745
|
+
- `@uploadista/data-stores-azure` - Azure Blob Storage
|
|
746
|
+
- `@uploadista/data-stores-gcs` - Google Cloud Storage
|
|
747
|
+
- `@uploadista/data-stores-filesystem` - Local filesystem
|
|
748
|
+
|
|
749
|
+
### DataStore Interface
|
|
750
|
+
|
|
751
|
+
```typescript
|
|
752
|
+
type DataStore<TData> = {
|
|
753
|
+
readonly bucket?: string;
|
|
754
|
+
readonly path?: string;
|
|
755
|
+
readonly create: (file: TData) => Effect.Effect<TData, UploadistaError>;
|
|
756
|
+
readonly remove: (file_id: string) => Effect.Effect<void, UploadistaError>;
|
|
757
|
+
readonly read: (file_id: string) => Effect.Effect<Uint8Array, UploadistaError>;
|
|
758
|
+
readonly write: (
|
|
759
|
+
options: DataStoreWriteOptions,
|
|
760
|
+
dependencies: { onProgress?: (chunkSize: number) => void }
|
|
761
|
+
) => Effect.Effect<number, UploadistaError>;
|
|
762
|
+
readonly deleteExpired?: Effect.Effect<number, UploadistaError>;
|
|
763
|
+
readonly getCapabilities: () => DataStoreCapabilities;
|
|
764
|
+
readonly validateUploadStrategy: (
|
|
765
|
+
strategy: UploadStrategy
|
|
766
|
+
) => Effect.Effect<boolean, never>;
|
|
767
|
+
};
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
### Using DataStores
|
|
771
|
+
|
|
772
|
+
```typescript
|
|
773
|
+
import { createDataStoreLayer } from "@uploadista/core/types";
|
|
774
|
+
import { S3DataStore } from "@uploadista/data-stores-s3";
|
|
775
|
+
|
|
776
|
+
// Single store
|
|
777
|
+
const dataStoreLayer = await createDataStoreLayer(s3Store);
|
|
778
|
+
|
|
779
|
+
// Multiple stores with routing
|
|
780
|
+
const dataStoreLayer = await createDataStoreLayer({
|
|
781
|
+
stores: {
|
|
782
|
+
"s3-prod": s3Store,
|
|
783
|
+
"azure-backup": azureStore,
|
|
784
|
+
},
|
|
785
|
+
default: "s3-prod"
|
|
786
|
+
});
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
## KV Stores
|
|
790
|
+
|
|
791
|
+
`@uploadista/core` defines the `KvStore` interface for metadata storage. Implementations are provided in separate packages:
|
|
792
|
+
|
|
793
|
+
- `@uploadista/kv-stores-cloudflare-kv` - Cloudflare KV
|
|
794
|
+
- `@uploadista/kv-stores-cloudflare-do` - Cloudflare Durable Objects
|
|
795
|
+
- `@uploadista/kv-stores-redis` - Redis
|
|
796
|
+
- `@uploadista/kv-stores-ioredis` - IORedis
|
|
797
|
+
- `@uploadista/kv-stores-memory` - In-memory (for testing)
|
|
798
|
+
- `@uploadista/kv-stores-filesystem` - File-based
|
|
799
|
+
|
|
800
|
+
### KvStore Interface
|
|
801
|
+
|
|
802
|
+
```typescript
|
|
803
|
+
type KvStore<TData> = {
|
|
804
|
+
readonly get: (key: string) => Effect.Effect<TData, UploadistaError>;
|
|
805
|
+
readonly set: (key: string, value: TData) => Effect.Effect<void, UploadistaError>;
|
|
806
|
+
readonly delete: (key: string) => Effect.Effect<void, UploadistaError>;
|
|
807
|
+
readonly list?: () => Effect.Effect<Array<string>, UploadistaError>;
|
|
808
|
+
};
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
### Creating Typed KV Stores
|
|
812
|
+
|
|
813
|
+
```typescript
|
|
814
|
+
import { TypedKvStore, jsonSerializer } from "@uploadista/core/types";
|
|
815
|
+
|
|
816
|
+
const uploadStore = new TypedKvStore<UploadFile>(
|
|
817
|
+
baseKvStore,
|
|
818
|
+
"uploadista:upload:",
|
|
819
|
+
jsonSerializer.serialize,
|
|
820
|
+
jsonSerializer.deserialize
|
|
821
|
+
);
|
|
822
|
+
|
|
823
|
+
// Use with Effect
|
|
824
|
+
const file = yield* uploadStore.get("file-123");
|
|
825
|
+
yield* uploadStore.set("file-123", updatedFile);
|
|
826
|
+
yield* uploadStore.delete("file-123");
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
## Effect-TS Patterns
|
|
830
|
+
|
|
831
|
+
### Dependency Injection
|
|
832
|
+
|
|
833
|
+
Use Effect's Context system to inject dependencies:
|
|
834
|
+
|
|
835
|
+
```typescript
|
|
836
|
+
import { Effect, Context, Layer } from "effect";
|
|
837
|
+
|
|
838
|
+
// Define your service
|
|
839
|
+
class MyService extends Context.Tag("MyService")<
|
|
840
|
+
MyService,
|
|
841
|
+
{ doSomething: () => Effect.Effect<string, never> }
|
|
842
|
+
>() {}
|
|
843
|
+
|
|
844
|
+
// Create implementation
|
|
845
|
+
const myServiceLive = Layer.succeed(MyService, {
|
|
846
|
+
doSomething: () => Effect.succeed("done")
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
// Use in program
|
|
850
|
+
const program = Effect.gen(function* () {
|
|
851
|
+
const service = yield* MyService;
|
|
852
|
+
const result = yield* service.doSomething();
|
|
853
|
+
console.log(result);
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
// Run with dependencies
|
|
857
|
+
Effect.runPromise(program.pipe(Effect.provide(myServiceLive)));
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
### Error Handling
|
|
861
|
+
|
|
862
|
+
All operations return Effects that can fail with `UploadistaError`:
|
|
863
|
+
|
|
864
|
+
```typescript
|
|
865
|
+
import { Effect } from "effect";
|
|
866
|
+
|
|
867
|
+
const program = Effect.gen(function* () {
|
|
868
|
+
const uploadServer = yield* UploadServer;
|
|
869
|
+
|
|
870
|
+
// Try to upload
|
|
871
|
+
const result = yield* uploadServer.upload(file, clientId, stream).pipe(
|
|
872
|
+
Effect.catchTag("UploadistaError", (error) => {
|
|
873
|
+
console.error("Upload failed:", error.body);
|
|
874
|
+
// Handle error or provide fallback
|
|
875
|
+
return Effect.succeed(fallbackFile);
|
|
876
|
+
})
|
|
877
|
+
);
|
|
878
|
+
|
|
879
|
+
return result;
|
|
880
|
+
});
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
### Combining Effects
|
|
884
|
+
|
|
885
|
+
Chain multiple operations:
|
|
886
|
+
|
|
887
|
+
```typescript
|
|
888
|
+
const program = Effect.gen(function* () {
|
|
889
|
+
const uploadServer = yield* UploadServer;
|
|
890
|
+
const flowServer = yield* FlowServer;
|
|
891
|
+
|
|
892
|
+
// Upload file
|
|
893
|
+
const file = yield* uploadServer.upload(inputFile, clientId, stream);
|
|
894
|
+
|
|
895
|
+
// Process with flow
|
|
896
|
+
const job = yield* flowServer.runFlow({
|
|
897
|
+
flowId: "process-image",
|
|
898
|
+
storageId: file.storage.id,
|
|
899
|
+
clientId,
|
|
900
|
+
inputs: { "input-1": file }
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
// Wait for completion (in real app, use WebSocket events)
|
|
904
|
+
let status = yield* flowServer.getJobStatus(job.id);
|
|
905
|
+
while (status.status === "running") {
|
|
906
|
+
yield* Effect.sleep(1000);
|
|
907
|
+
status = yield* flowServer.getJobStatus(job.id);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
if (status.status === "completed") {
|
|
911
|
+
return status.result;
|
|
912
|
+
} else {
|
|
913
|
+
return yield* Effect.fail(
|
|
914
|
+
UploadistaError.fromCode("FLOW_JOB_ERROR", {
|
|
915
|
+
body: status.error || "Flow failed"
|
|
916
|
+
})
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
## Testing
|
|
923
|
+
|
|
924
|
+
The core package includes unit tests using Vitest:
|
|
925
|
+
|
|
926
|
+
```bash
|
|
927
|
+
# Run tests
|
|
928
|
+
pnpm test
|
|
929
|
+
|
|
930
|
+
# Run tests in watch mode
|
|
931
|
+
pnpm test:watch
|
|
932
|
+
|
|
933
|
+
# Run tests once
|
|
934
|
+
pnpm test:run
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
### Testing with Effect
|
|
938
|
+
|
|
939
|
+
Use Effect's testing utilities:
|
|
940
|
+
|
|
941
|
+
```typescript
|
|
942
|
+
import { Effect, Layer } from "effect";
|
|
943
|
+
import { describe, it, expect } from "vitest";
|
|
944
|
+
|
|
945
|
+
describe("MyFlow", () => {
|
|
946
|
+
it("should process file", async () => {
|
|
947
|
+
const testLayer = Layer.mergeAll(
|
|
948
|
+
mockDataStore,
|
|
949
|
+
mockKvStore,
|
|
950
|
+
mockEventEmitter
|
|
951
|
+
);
|
|
952
|
+
|
|
953
|
+
const result = await Effect.runPromise(
|
|
954
|
+
program.pipe(Effect.provide(testLayer))
|
|
955
|
+
);
|
|
956
|
+
|
|
957
|
+
expect(result).toBeDefined();
|
|
958
|
+
});
|
|
959
|
+
});
|
|
960
|
+
```
|
|
961
|
+
|
|
962
|
+
## Type Safety
|
|
963
|
+
|
|
964
|
+
Full TypeScript support with strict typing:
|
|
965
|
+
|
|
966
|
+
```typescript
|
|
967
|
+
import type { Flow, FlowNode, FlowEdge } from "@uploadista/core/flow";
|
|
968
|
+
import type { UploadFile, DataStore } from "@uploadista/core/types";
|
|
969
|
+
import { z } from "zod";
|
|
970
|
+
|
|
971
|
+
// Define schemas
|
|
972
|
+
const myInputSchema = z.object({
|
|
973
|
+
file: z.instanceof(Uint8Array),
|
|
974
|
+
metadata: z.record(z.string()),
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
type MyInput = z.infer<typeof myInputSchema>;
|
|
978
|
+
|
|
979
|
+
// TypeScript ensures type safety
|
|
980
|
+
const node = createFlowNode<MyInput, ProcessedOutput>({
|
|
981
|
+
inputSchema: myInputSchema,
|
|
982
|
+
outputSchema: outputSchema,
|
|
983
|
+
run: ({ data }) => {
|
|
984
|
+
// data is typed as MyInput
|
|
985
|
+
return Effect.succeed({
|
|
986
|
+
type: "complete",
|
|
987
|
+
data: processedOutput // Must match ProcessedOutput type
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
});
|
|
991
|
+
```
|
|
992
|
+
|
|
993
|
+
## Best Practices
|
|
994
|
+
|
|
995
|
+
### 1. Use Effect-TS Patterns
|
|
996
|
+
|
|
997
|
+
Always use Effect for async operations:
|
|
998
|
+
|
|
999
|
+
```typescript
|
|
1000
|
+
// Good
|
|
1001
|
+
const upload = yield* uploadServer.upload(file, clientId, stream);
|
|
1002
|
+
|
|
1003
|
+
// Avoid
|
|
1004
|
+
const upload = await Effect.runPromise(
|
|
1005
|
+
uploadServer.upload(file, clientId, stream)
|
|
1006
|
+
);
|
|
1007
|
+
```
|
|
1008
|
+
|
|
1009
|
+
### 2. Handle Errors Properly
|
|
1010
|
+
|
|
1011
|
+
Use Effect's error handling:
|
|
1012
|
+
|
|
1013
|
+
```typescript
|
|
1014
|
+
const program = uploadServer.upload(file, clientId, stream).pipe(
|
|
1015
|
+
Effect.retry({ times: 3 }),
|
|
1016
|
+
Effect.timeout(30000),
|
|
1017
|
+
Effect.catchAll((error) => {
|
|
1018
|
+
// Log and handle
|
|
1019
|
+
return fallbackEffect;
|
|
1020
|
+
})
|
|
1021
|
+
);
|
|
1022
|
+
```
|
|
1023
|
+
|
|
1024
|
+
### 3. Validate Inputs
|
|
1025
|
+
|
|
1026
|
+
Always validate inputs with Zod schemas:
|
|
1027
|
+
|
|
1028
|
+
```typescript
|
|
1029
|
+
const inputSchema = z.object({
|
|
1030
|
+
filename: z.string().min(1),
|
|
1031
|
+
size: z.number().positive(),
|
|
1032
|
+
mimeType: z.string(),
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
// Schema validation is automatic in Flow nodes
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
### 4. Use Typed Stores
|
|
1039
|
+
|
|
1040
|
+
Create typed wrappers for KV stores:
|
|
1041
|
+
|
|
1042
|
+
```typescript
|
|
1043
|
+
const uploadStore = new TypedKvStore<UploadFile>(
|
|
1044
|
+
baseStore,
|
|
1045
|
+
"uploads:",
|
|
1046
|
+
jsonSerializer.serialize,
|
|
1047
|
+
jsonSerializer.deserialize
|
|
1048
|
+
);
|
|
1049
|
+
```
|
|
1050
|
+
|
|
1051
|
+
### 5. Manage Resources
|
|
1052
|
+
|
|
1053
|
+
Use Effect's resource management:
|
|
1054
|
+
|
|
1055
|
+
```typescript
|
|
1056
|
+
const program = Effect.acquireUseRelease(
|
|
1057
|
+
// Acquire
|
|
1058
|
+
Effect.sync(() => openFile(path)),
|
|
1059
|
+
// Use
|
|
1060
|
+
(file) => processFile(file),
|
|
1061
|
+
// Release
|
|
1062
|
+
(file) => Effect.sync(() => file.close())
|
|
1063
|
+
);
|
|
1064
|
+
```
|
|
1065
|
+
|
|
1066
|
+
## Related Packages
|
|
1067
|
+
|
|
1068
|
+
### Data Stores
|
|
1069
|
+
- `@uploadista/data-stores-s3` - AWS S3 storage
|
|
1070
|
+
- `@uploadista/data-stores-azure` - Azure Blob Storage
|
|
1071
|
+
- `@uploadista/data-stores-gcs` - Google Cloud Storage
|
|
1072
|
+
- `@uploadista/data-stores-filesystem` - Local filesystem storage
|
|
1073
|
+
|
|
1074
|
+
### KV Stores
|
|
1075
|
+
- `@uploadista/kv-stores-cloudflare-kv` - Cloudflare KV
|
|
1076
|
+
- `@uploadista/kv-stores-cloudflare-do` - Cloudflare Durable Objects
|
|
1077
|
+
- `@uploadista/kv-stores-redis` - Redis
|
|
1078
|
+
- `@uploadista/kv-stores-ioredis` - IORedis
|
|
1079
|
+
- `@uploadista/kv-stores-memory` - In-memory store
|
|
1080
|
+
- `@uploadista/kv-stores-filesystem` - File-based store
|
|
1081
|
+
|
|
1082
|
+
### Flow Nodes
|
|
1083
|
+
- `@uploadista/flow-input-nodes` - File input nodes
|
|
1084
|
+
- `@uploadista/flow-output-nodes` - Storage output nodes
|
|
1085
|
+
- `@uploadista/flow-image-nodes` - Image processing nodes
|
|
1086
|
+
- `@uploadista/flow-utility-nodes` - Utility nodes (conditional, merge, etc.)
|
|
1087
|
+
|
|
1088
|
+
### Client & Server
|
|
1089
|
+
- `@uploadista/client` - Browser upload client
|
|
1090
|
+
- `@uploadista/server` - Server-side utilities
|
|
1091
|
+
|
|
1092
|
+
## Development
|
|
1093
|
+
|
|
1094
|
+
### Build
|
|
1095
|
+
|
|
1096
|
+
```bash
|
|
1097
|
+
pnpm build
|
|
1098
|
+
```
|
|
1099
|
+
|
|
1100
|
+
### Lint and Format
|
|
1101
|
+
|
|
1102
|
+
```bash
|
|
1103
|
+
pnpm lint
|
|
1104
|
+
pnpm format
|
|
1105
|
+
pnpm check
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
### Type Check
|
|
1109
|
+
|
|
1110
|
+
```bash
|
|
1111
|
+
pnpm typecheck
|
|
1112
|
+
```
|
|
1113
|
+
|
|
1114
|
+
## License
|
|
1115
|
+
|
|
1116
|
+
See the main repository for license information.
|
|
1117
|
+
|
|
1118
|
+
## Contributing
|
|
1119
|
+
|
|
1120
|
+
See the main repository for contribution guidelines.
|