1- import type { FetchHttpClient } from "@effect/platform" ;
1+ import type { FetchHttpClient , HttpClientError } from "@effect/platform" ;
22import {
33 HttpClient ,
44 HttpClientRequest ,
55 HttpClientResponse ,
66} from "@effect/platform" ;
77import * as Arr from "effect/Array" ;
8+ import * as Cause from "effect/Cause" ;
89import * as Effect from "effect/Effect" ;
9- import type * as ManagedRuntime from "effect/ManagedRuntime" ;
10- import * as Predicate from "effect/Predicate " ;
10+ import type { ManagedRuntime } from "effect/ManagedRuntime" ;
11+ import type { ParseError } from "effect/ParseResult " ;
1112import * as Redacted from "effect/Redacted" ;
1213import * as S from "effect/Schema" ;
1314
14- import type {
15- ACL ,
16- FetchEsque ,
17- MaybeUrl ,
18- SerializedUploadThingError ,
19- } from "@uploadthing/shared" ;
15+ import type { ACL , FetchEsque , MaybeUrl } from "@uploadthing/shared" ;
2016import { parseTimeToSeconds , UploadThingError } from "@uploadthing/shared" ;
2117
2218import { ApiUrl , UPLOADTHING_VERSION , UTToken } from "../internal/config" ;
@@ -36,14 +32,14 @@ import type {
3632 UTApiOptions ,
3733} from "./types" ;
3834import { UTFile } from "./ut-file" ;
39- import { downloadFiles , guardServerOnly , uploadFilesInternal } from "./utils" ;
35+ import { downloadFile , guardServerOnly , uploadFile } from "./utils" ;
4036
4137export { UTFile } ;
4238
4339export class UTApi {
4440 private fetch : FetchEsque ;
4541 private defaultKeyType : "fileKey" | "customId" ;
46- private runtime : ManagedRuntime . ManagedRuntime <
42+ private runtime : ManagedRuntime <
4743 HttpClient . HttpClient | FetchHttpClient . Fetch ,
4844 UploadThingError
4945 > ;
@@ -83,18 +79,38 @@ export class UTApi {
8379 Effect . flatMap ( HttpClientResponse . schemaBodyJson ( responseSchema ) ) ,
8480 Effect . scoped ,
8581 ) ;
86- } ) . pipe ( Effect . withLogSpan ( "utapi.#requestUploadThing" ) ) ;
82+ } ) . pipe (
83+ Effect . catchTag (
84+ "ConfigError" ,
85+ ( e ) =>
86+ new UploadThingError ( {
87+ code : "INVALID_SERVER_CONFIG" ,
88+ message :
89+ "There was an error with the server configuration. More info can be found on this error's `cause` property" ,
90+ cause : e ,
91+ } ) ,
92+ ) ,
93+ Effect . withLogSpan ( "utapi.#requestUploadThing" ) ,
94+ ) ;
8795
88- private executeAsync = async < A , E > (
89- program : Effect . Effect < A , E , HttpClient . HttpClient > ,
96+ private executeAsync = async < A > (
97+ program : Effect . Effect <
98+ A ,
99+ UploadThingError | ParseError | HttpClientError . HttpClientError ,
100+ HttpClient . HttpClient
101+ > ,
90102 signal ?: AbortSignal ,
91103 ) => {
92- const result = await program . pipe (
104+ const exit = await program . pipe (
93105 Effect . withLogSpan ( "utapi.#executeAsync" ) ,
94- ( e ) => this . runtime . runPromise ( e , signal ? { signal } : undefined ) ,
106+ ( e ) => this . runtime . runPromiseExit ( e , signal ? { signal } : undefined ) ,
95107 ) ;
96108
97- return result ;
109+ if ( exit . _tag === "Failure" ) {
110+ throw Cause . squash ( exit . cause ) ;
111+ }
112+
113+ return exit . value ;
98114 } ;
99115
100116 /**
@@ -117,31 +133,34 @@ export class UTApi {
117133 files : FileEsque [ ] ,
118134 opts ?: UploadFilesOptions ,
119135 ) : Promise < UploadFileResult [ ] > ;
120- async uploadFiles (
136+ uploadFiles (
121137 files : FileEsque | FileEsque [ ] ,
122138 opts ?: UploadFilesOptions ,
123139 ) : Promise < UploadFileResult | UploadFileResult [ ] > {
124140 guardServerOnly ( ) ;
125141
126- const uploads = await this . executeAsync (
127- Effect . flatMap (
128- uploadFilesInternal ( {
129- files : Arr . ensure ( files ) ,
130- contentDisposition : opts ?. contentDisposition ?? "inline" ,
131- acl : opts ?. acl ,
142+ const program : Effect . Effect <
143+ UploadFileResult | UploadFileResult [ ] ,
144+ never ,
145+ HttpClient . HttpClient
146+ > = Effect . forEach ( Arr . ensure ( files ) , ( file ) =>
147+ uploadFile ( file , opts ?? { } ) . pipe (
148+ Effect . match ( {
149+ onSuccess : ( data ) => ( { data, error : null } ) ,
150+ onFailure : ( error ) => ( { data : null , error } ) ,
132151 } ) ,
133- ( ups ) => Effect . succeed ( Array . isArray ( files ) ? ups : ups [ 0 ] ) ,
134- ) . pipe (
135- Effect . tap ( ( res ) =>
136- Effect . logDebug ( "Finished uploading" ) . pipe (
137- Effect . annotateLogs ( "uploadResult" , res ) ,
138- ) ,
152+ ) ,
153+ ) . pipe (
154+ Effect . map ( ( ups ) => ( Array . isArray ( files ) ? ups : ups [ 0 ] ) ) ,
155+ Effect . tap ( ( res ) =>
156+ Effect . logDebug ( "Finished uploading" ) . pipe (
157+ Effect . annotateLogs ( "uploadResult" , res ) ,
139158 ) ,
140- Effect . withLogSpan ( "uploadFiles" ) ,
141159 ) ,
142- opts ?. signal ,
160+ Effect . withLogSpan ( "uploadFiles" ) ,
143161 ) ;
144- return uploads ;
162+
163+ return this . executeAsync ( program , opts ?. signal ) ;
145164 }
146165
147166 /**
@@ -165,49 +184,37 @@ export class UTApi {
165184 urls : ( MaybeUrl | UrlWithOverrides ) [ ] ,
166185 opts ?: UploadFilesOptions ,
167186 ) : Promise < UploadFileResult [ ] > ;
168- async uploadFilesFromUrl (
187+ uploadFilesFromUrl (
169188 urls : MaybeUrl | UrlWithOverrides | ( MaybeUrl | UrlWithOverrides ) [ ] ,
170189 opts ?: UploadFilesOptions ,
171190 ) : Promise < UploadFileResult | UploadFileResult [ ] > {
172191 guardServerOnly ( ) ;
173192
174- const downloadErrors : Record < number , SerializedUploadThingError > = { } ;
175- const arr = Arr . ensure ( urls ) ;
176-
177- const program = Effect . gen ( function * ( ) {
178- const downloadedFiles = yield * downloadFiles ( arr , downloadErrors ) . pipe (
179- Effect . map ( ( files ) => Arr . filter ( files , Predicate . isNotNullable ) ) ,
180- ) ;
181-
182- yield * Effect . logDebug (
183- `Downloaded ${ downloadedFiles . length } /${ arr . length } files` ,
184- ) . pipe ( Effect . annotateLogs ( "downloadedFiles" , downloadedFiles ) ) ;
185-
186- const uploads = yield * uploadFilesInternal ( {
187- files : downloadedFiles ,
188- contentDisposition : opts ?. contentDisposition ?? "inline" ,
189- acl : opts ?. acl ,
190- } ) ;
191-
192- /** Put it all back together, preserve the order of files */
193- const responses = arr . map ( ( _ , index ) => {
194- if ( downloadErrors [ index ] ) {
195- return { data : null , error : downloadErrors [ index ] } ;
196- }
197- return uploads . shift ( ) ! ;
198- } ) ;
199-
200- /** Return single object or array based on input urls */
201- const uploadFileResponse = Array . isArray ( urls ) ? responses : responses [ 0 ] ;
202- yield * Effect . logDebug ( "Finished uploading" ) . pipe (
203- Effect . annotateLogs ( "uploadResult" , uploadFileResponse ) ,
204- Effect . withLogSpan ( "utapi.uploadFilesFromUrl" ) ,
205- ) ;
206-
207- return uploadFileResponse ;
208- } ) . pipe ( Effect . withLogSpan ( "uploadFilesFromUrl" ) ) ;
193+ const program : Effect . Effect <
194+ UploadFileResult | UploadFileResult [ ] ,
195+ never ,
196+ HttpClient . HttpClient
197+ > = Effect . forEach ( Arr . ensure ( urls ) , ( url ) =>
198+ downloadFile ( url ) . pipe (
199+ Effect . flatMap ( ( file ) => uploadFile ( file , opts ?? { } ) ) ,
200+ Effect . match ( {
201+ onSuccess : ( data ) => ( { data, error : null } ) ,
202+ onFailure : ( error ) => ( { data : null , error } ) ,
203+ } ) ,
204+ ) ,
205+ )
206+ . pipe (
207+ Effect . map ( ( ups ) => ( Array . isArray ( urls ) ? ups : ups [ 0 ] ) ) ,
208+ Effect . tap ( ( res ) =>
209+ Effect . logDebug ( "Finished uploading" ) . pipe (
210+ Effect . annotateLogs ( "uploadResult" , res ) ,
211+ ) ,
212+ ) ,
213+ Effect . withLogSpan ( "uploadFiles" ) ,
214+ )
215+ . pipe ( Effect . withLogSpan ( "uploadFilesFromUrl" ) ) ;
209216
210- return await this . executeAsync ( program , opts ?. signal ) ;
217+ return this . executeAsync ( program , opts ?. signal ) ;
211218 }
212219
213220 /**
0 commit comments