@depup/uploadthing 7.7.4-depup.0
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/LICENSE +21 -0
- package/README.md +33 -0
- package/changes.json +18 -0
- package/client/index.cjs +331 -0
- package/client/index.d.cts +36 -0
- package/client/index.d.cts.map +1 -0
- package/client/index.d.ts +36 -0
- package/client/index.d.ts.map +1 -0
- package/client/index.js +286 -0
- package/client/index.js.map +1 -0
- package/client-future/index.cjs +426 -0
- package/client-future/index.d.cts +373 -0
- package/client-future/index.d.cts.map +1 -0
- package/client-future/index.d.ts +373 -0
- package/client-future/index.d.ts.map +1 -0
- package/client-future/index.js +383 -0
- package/client-future/index.js.map +1 -0
- package/dist/chunk-CUT6urMc.cjs +30 -0
- package/dist/deprecations-DPGpmqha.cjs +13 -0
- package/dist/deprecations-pLmw6Ytd.js +8 -0
- package/dist/deprecations-pLmw6Ytd.js.map +1 -0
- package/dist/package-BQ_k22T9.cjs +11 -0
- package/dist/package-DpScpvTA.js +6 -0
- package/dist/package-DpScpvTA.js.map +1 -0
- package/dist/shared-schemas-BmG5ARoX.js +82 -0
- package/dist/shared-schemas-BmG5ARoX.js.map +1 -0
- package/dist/shared-schemas-CG9VaBtT.cjs +129 -0
- package/dist/to-web-request-BQtxSXgE.cjs +98 -0
- package/dist/to-web-request-DhP0wXG-.js +87 -0
- package/dist/to-web-request-DhP0wXG-.js.map +1 -0
- package/dist/types-Bs3w2d_3.d.ts +627 -0
- package/dist/types-Bs3w2d_3.d.ts.map +1 -0
- package/dist/types-DiVC1t2V.d.cts +625 -0
- package/dist/types-DiVC1t2V.d.cts.map +1 -0
- package/dist/upload-builder-BUa7tovh.d.cts +32 -0
- package/dist/upload-builder-BUa7tovh.d.cts.map +1 -0
- package/dist/upload-builder-BcFawEj0.d.ts +32 -0
- package/dist/upload-builder-BcFawEj0.d.ts.map +1 -0
- package/dist/upload-builder-BlFOAnsv.js +699 -0
- package/dist/upload-builder-BlFOAnsv.js.map +1 -0
- package/dist/upload-builder-D6Ken9H0.cjs +794 -0
- package/dist/ut-reporter-BHoyNnzW.cjs +120 -0
- package/dist/ut-reporter-Dlppchbx.js +103 -0
- package/dist/ut-reporter-Dlppchbx.js.map +1 -0
- package/effect-platform/index.cjs +22 -0
- package/effect-platform/index.d.cts +54 -0
- package/effect-platform/index.d.cts.map +1 -0
- package/effect-platform/index.d.ts +54 -0
- package/effect-platform/index.d.ts.map +1 -0
- package/effect-platform/index.js +19 -0
- package/effect-platform/index.js.map +1 -0
- package/express/index.cjs +30 -0
- package/express/index.d.cts +28 -0
- package/express/index.d.cts.map +1 -0
- package/express/index.d.ts +28 -0
- package/express/index.d.ts.map +1 -0
- package/express/index.js +27 -0
- package/express/index.js.map +1 -0
- package/fastify/index.cjs +27 -0
- package/fastify/index.d.cts +28 -0
- package/fastify/index.d.cts.map +1 -0
- package/fastify/index.d.ts +28 -0
- package/fastify/index.d.ts.map +1 -0
- package/fastify/index.js +24 -0
- package/fastify/index.js.map +1 -0
- package/h3/index.cjs +20 -0
- package/h3/index.d.cts +28 -0
- package/h3/index.d.cts.map +1 -0
- package/h3/index.d.ts +28 -0
- package/h3/index.d.ts.map +1 -0
- package/h3/index.js +17 -0
- package/h3/index.js.map +1 -0
- package/next/index.cjs +22 -0
- package/next/index.d.cts +30 -0
- package/next/index.d.cts.map +1 -0
- package/next/index.d.ts +30 -0
- package/next/index.d.ts.map +1 -0
- package/next/index.js +19 -0
- package/next/index.js.map +1 -0
- package/next-legacy/index.cjs +28 -0
- package/next-legacy/index.d.cts +28 -0
- package/next-legacy/index.d.cts.map +1 -0
- package/next-legacy/index.d.ts +28 -0
- package/next-legacy/index.d.ts.map +1 -0
- package/next-legacy/index.js +25 -0
- package/next-legacy/index.js.map +1 -0
- package/package.json +210 -0
- package/remix/index.cjs +22 -0
- package/remix/index.d.cts +30 -0
- package/remix/index.d.cts.map +1 -0
- package/remix/index.d.ts +30 -0
- package/remix/index.d.ts.map +1 -0
- package/remix/index.js +19 -0
- package/remix/index.js.map +1 -0
- package/server/index.cjs +414 -0
- package/server/index.d.cts +211 -0
- package/server/index.d.cts.map +1 -0
- package/server/index.d.ts +213 -0
- package/server/index.d.ts.map +1 -0
- package/server/index.js +405 -0
- package/server/index.js.map +1 -0
- package/tw/index.cjs +70 -0
- package/tw/index.d.cts +29 -0
- package/tw/index.d.cts.map +1 -0
- package/tw/index.d.ts +29 -0
- package/tw/index.d.ts.map +1 -0
- package/tw/index.js +74 -0
- package/tw/index.js.map +1 -0
- package/tw/v4.css +11 -0
- package/types/index.cjs +3 -0
- package/types/index.d.cts +2 -0
- package/types/index.d.ts +2 -0
- package/types/index.js +3 -0
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
import { version } from "./package-DpScpvTA.js";
|
|
2
|
+
import { logDeprecationWarning } from "./deprecations-pLmw6Ytd.js";
|
|
3
|
+
import { ActionType, CallbackResultResponse, MetadataFetchResponse, MetadataFetchStreamPart, UploadActionPayload, UploadThingHook, UploadThingToken, UploadedFileData } from "./shared-schemas-BmG5ARoX.js";
|
|
4
|
+
import { InvalidRouteConfigError, UploadThingError, bytesToFileSize, fileSizeToBytes, fillInputRouteConfig, filterDefinedObjectValues, generateKey, generateSignedURL, getStatusCodeFromError, matchFileType, objectKeys, verifySignature } from "@uploadthing/shared";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
import * as HttpApp from "@effect/platform/HttpApp";
|
|
7
|
+
import * as HttpBody from "@effect/platform/HttpBody";
|
|
8
|
+
import * as HttpClient from "@effect/platform/HttpClient";
|
|
9
|
+
import * as HttpClientRequest from "@effect/platform/HttpClientRequest";
|
|
10
|
+
import * as HttpClientResponse from "@effect/platform/HttpClientResponse";
|
|
11
|
+
import * as HttpRouter from "@effect/platform/HttpRouter";
|
|
12
|
+
import * as HttpServerRequest from "@effect/platform/HttpServerRequest";
|
|
13
|
+
import * as HttpServerResponse from "@effect/platform/HttpServerResponse";
|
|
14
|
+
import * as Config from "effect/Config";
|
|
15
|
+
import * as Context from "effect/Context";
|
|
16
|
+
import * as Match from "effect/Match";
|
|
17
|
+
import * as Redacted from "effect/Redacted";
|
|
18
|
+
import * as S from "effect/Schema";
|
|
19
|
+
import * as ConfigProvider from "effect/ConfigProvider";
|
|
20
|
+
import * as Stream from "effect/Stream";
|
|
21
|
+
import * as ConfigError from "effect/ConfigError";
|
|
22
|
+
import * as Either$1 from "effect/Either";
|
|
23
|
+
import * as Layer from "effect/Layer";
|
|
24
|
+
import * as Logger from "effect/Logger";
|
|
25
|
+
import * as LogLevel from "effect/LogLevel";
|
|
26
|
+
import * as Cause from "effect/Cause";
|
|
27
|
+
import * as Data from "effect/Data";
|
|
28
|
+
import * as Runtime from "effect/Runtime";
|
|
29
|
+
import * as FetchHttpClient from "@effect/platform/FetchHttpClient";
|
|
30
|
+
import * as Headers from "@effect/platform/Headers";
|
|
31
|
+
import * as FiberRef from "effect/FiberRef";
|
|
32
|
+
import * as ManagedRuntime from "effect/ManagedRuntime";
|
|
33
|
+
|
|
34
|
+
//#region src/_internal/config.ts
|
|
35
|
+
/**
|
|
36
|
+
* Merge in `import.meta.env` to the built-in `process.env` provider
|
|
37
|
+
* Prefix keys with `UPLOADTHING_` so we can reference just the name.
|
|
38
|
+
* @example
|
|
39
|
+
* process.env.UPLOADTHING_TOKEN = "foo"
|
|
40
|
+
* Config.string("token"); // Config<"foo">
|
|
41
|
+
*/
|
|
42
|
+
const envProvider = ConfigProvider.fromEnv().pipe(ConfigProvider.orElse(() => ConfigProvider.fromMap(new Map(Object.entries(filterDefinedObjectValues(import.meta?.env ?? {}))), { pathDelim: "_" })), ConfigProvider.nested("uploadthing"), ConfigProvider.constantCase);
|
|
43
|
+
/**
|
|
44
|
+
* Config provider that merges the options from the object
|
|
45
|
+
* and environment variables prefixed with `UPLOADTHING_`.
|
|
46
|
+
* @remarks Options take precedence over environment variables.
|
|
47
|
+
*/
|
|
48
|
+
const configProvider = (options) => ConfigProvider.fromJson(options ?? {}).pipe(ConfigProvider.orElse(() => envProvider));
|
|
49
|
+
const IsDevelopment = Config.boolean("isDev").pipe(Config.orElse(() => Config.succeed(typeof process !== "undefined" ? process.env.NODE_ENV : void 0).pipe(Config.map((_) => _ === "development"))), Config.withDefault(false));
|
|
50
|
+
const UTToken = S.Config("token", UploadThingToken).pipe(Effect.catchTags({ ConfigError: (e) => new UploadThingError({
|
|
51
|
+
code: e._op === "InvalidData" ? "INVALID_SERVER_CONFIG" : "MISSING_ENV",
|
|
52
|
+
message: e._op === "InvalidData" ? "Invalid token. A token is a base64 encoded JSON object matching { apiKey: string, appId: string, regions: string[] }." : "Missing token. Please set the `UPLOADTHING_TOKEN` environment variable or provide a token manually through config.",
|
|
53
|
+
cause: e
|
|
54
|
+
}) }));
|
|
55
|
+
const ApiUrl = Config.string("apiUrl").pipe(Config.withDefault("https://api.uploadthing.com"), Config.mapAttempt((_) => new URL(_)), Config.map((url) => url.href.replace(/\/$/, "")));
|
|
56
|
+
const IngestUrl = Effect.fn(function* (preferredRegion) {
|
|
57
|
+
const { regions, ingestHost } = yield* UTToken;
|
|
58
|
+
const region = preferredRegion ? regions.find((r) => r === preferredRegion) ?? regions[0] : regions[0];
|
|
59
|
+
return yield* Config.string("ingestUrl").pipe(Config.withDefault(`https://${region}.${ingestHost}`), Config.mapAttempt((_) => new URL(_)), Config.map((url) => url.href.replace(/\/$/, "")));
|
|
60
|
+
});
|
|
61
|
+
const UtfsHost = Config.string("utfsHost").pipe(Config.withDefault("utfs.io"));
|
|
62
|
+
const UfsHost = Config.string("ufsHost").pipe(Config.withDefault("ufs.sh"));
|
|
63
|
+
const UfsAppIdLocation = Config.literal("subdomain", "path")("ufsAppIdLocation").pipe(Config.withDefault("subdomain"));
|
|
64
|
+
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/_internal/error-formatter.ts
|
|
67
|
+
function defaultErrorFormatter(error) {
|
|
68
|
+
return { message: error.message };
|
|
69
|
+
}
|
|
70
|
+
function formatError(error, router) {
|
|
71
|
+
const firstSlug = Object.keys(router)[0];
|
|
72
|
+
const errorFormatter = firstSlug ? router[firstSlug]?.errorFormatter ?? defaultErrorFormatter : defaultErrorFormatter;
|
|
73
|
+
return errorFormatter(error);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/_internal/jsonl.ts
|
|
78
|
+
const handleJsonLineStream = (schema, onChunk) => (stream) => {
|
|
79
|
+
let buf = "";
|
|
80
|
+
return stream.pipe(Stream.decodeText(), Stream.mapEffect((chunk) => Effect.gen(function* () {
|
|
81
|
+
buf += chunk;
|
|
82
|
+
const parts = buf.split("\n");
|
|
83
|
+
const validChunks = [];
|
|
84
|
+
for (const part of parts) try {
|
|
85
|
+
validChunks.push(JSON.parse(part));
|
|
86
|
+
buf = buf.slice(part.length + 1);
|
|
87
|
+
} catch {}
|
|
88
|
+
yield* Effect.logDebug("Received chunks").pipe(Effect.annotateLogs("chunk", chunk), Effect.annotateLogs("parsedChunks", validChunks), Effect.annotateLogs("buf", buf));
|
|
89
|
+
return validChunks;
|
|
90
|
+
})), Stream.mapEffect(S.decodeUnknown(S.Array(schema))), Stream.mapEffect(Effect.forEach((part) => onChunk(part))), Stream.runDrain, Effect.withLogSpan("handleJsonLineStream"));
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/_internal/logger.ts
|
|
95
|
+
/**
|
|
96
|
+
* Config.logLevel counter-intuitively accepts LogLevel["label"]
|
|
97
|
+
* instead of a literal, ripping it and changing to accept literal
|
|
98
|
+
* Effect 4.0 will change this to accept a literal and then we can
|
|
99
|
+
* remove this and go back to the built-in validator.
|
|
100
|
+
*/
|
|
101
|
+
const ConfigLogLevel = (name) => {
|
|
102
|
+
const config = Config.mapOrFail(Config.string(), (literal) => {
|
|
103
|
+
const level = LogLevel.allLevels.find((level$1) => level$1._tag === literal);
|
|
104
|
+
return level === void 0 ? Either$1.left(ConfigError.InvalidData([], `Expected a log level but received ${literal}`)) : Either$1.right(level);
|
|
105
|
+
});
|
|
106
|
+
return name === void 0 ? config : Config.nested(config, name);
|
|
107
|
+
};
|
|
108
|
+
const withMinimalLogLevel = ConfigLogLevel("logLevel").pipe(Config.withDefault(LogLevel.Info), Effect.andThen((level) => Logger.minimumLogLevel(level)), Effect.tapError((e) => Effect.logError("Invalid log level").pipe(Effect.annotateLogs("error", e))), Effect.catchTag("ConfigError", (e) => new UploadThingError({
|
|
109
|
+
code: "INVALID_SERVER_CONFIG",
|
|
110
|
+
message: "Invalid server configuration",
|
|
111
|
+
cause: e
|
|
112
|
+
})), Layer.unwrapEffect);
|
|
113
|
+
const LogFormat = Config.literal("json", "logFmt", "structured", "pretty")("logFormat");
|
|
114
|
+
const withLogFormat = Effect.gen(function* () {
|
|
115
|
+
const isDev = yield* IsDevelopment;
|
|
116
|
+
const logFormat = yield* LogFormat.pipe(Config.withDefault(isDev ? "pretty" : "json"));
|
|
117
|
+
return Logger[logFormat];
|
|
118
|
+
}).pipe(Effect.catchTag("ConfigError", (e) => new UploadThingError({
|
|
119
|
+
code: "INVALID_SERVER_CONFIG",
|
|
120
|
+
message: "Invalid server configuration",
|
|
121
|
+
cause: e
|
|
122
|
+
})), Layer.unwrapEffect);
|
|
123
|
+
const logHttpClientResponse = (message, opts) => {
|
|
124
|
+
const mixin = opts?.mixin ?? "json";
|
|
125
|
+
const level = LogLevel.fromLiteral(opts?.level ?? "Debug");
|
|
126
|
+
return (response) => Effect.flatMap(mixin !== "None" ? response[mixin] : Effect.void, () => Effect.logWithLevel(level, `${message} (${response.status})`).pipe(Effect.annotateLogs("response", response)));
|
|
127
|
+
};
|
|
128
|
+
const logHttpClientError = (message) => (err) => err._tag === "ResponseError" ? logHttpClientResponse(message, { level: "Error" })(err.response) : Effect.logError(message).pipe(Effect.annotateLogs("error", err));
|
|
129
|
+
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region src/_internal/parser.ts
|
|
132
|
+
var ParserError = class extends Data.TaggedError("ParserError") {
|
|
133
|
+
message = "Input validation failed. The original error with it's validation issues is in the error cause.";
|
|
134
|
+
};
|
|
135
|
+
function getParseFn(parser) {
|
|
136
|
+
if ("parseAsync" in parser && typeof parser.parseAsync === "function")
|
|
137
|
+
/**
|
|
138
|
+
* Zod
|
|
139
|
+
* TODO (next major): Consider wrapping ZodError in ParserError
|
|
140
|
+
*/
|
|
141
|
+
return parser.parseAsync;
|
|
142
|
+
if (S.isSchema(parser))
|
|
143
|
+
/**
|
|
144
|
+
* Effect Schema
|
|
145
|
+
*/
|
|
146
|
+
return (value) => S.decodeUnknownPromise(parser)(value).catch((error) => {
|
|
147
|
+
throw new ParserError({ cause: Cause.squash(error[Runtime.FiberFailureCauseId]) });
|
|
148
|
+
});
|
|
149
|
+
if ("~standard" in parser)
|
|
150
|
+
/**
|
|
151
|
+
* Standard Schema
|
|
152
|
+
* TODO (next major): Consider moving this to the top of the function
|
|
153
|
+
*/
|
|
154
|
+
return async (value) => {
|
|
155
|
+
const result = await parser["~standard"].validate(value);
|
|
156
|
+
if (result.issues) throw new ParserError({ cause: result.issues });
|
|
157
|
+
return result.value;
|
|
158
|
+
};
|
|
159
|
+
throw new Error("Invalid parser");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region src/_internal/route-config.ts
|
|
164
|
+
var FileSizeMismatch = class extends Data.Error {
|
|
165
|
+
_tag = "FileSizeMismatch";
|
|
166
|
+
name = "FileSizeMismatchError";
|
|
167
|
+
constructor(type, max, actual) {
|
|
168
|
+
const reason = `You uploaded a ${type} file that was ${bytesToFileSize(actual)}, but the limit for that type is ${max}`;
|
|
169
|
+
super({ reason });
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
var FileCountMismatch = class extends Data.Error {
|
|
173
|
+
_tag = "FileCountMismatch";
|
|
174
|
+
name = "FileCountMismatchError";
|
|
175
|
+
constructor(type, boundtype, bound, actual) {
|
|
176
|
+
const reason = `You uploaded ${actual} file(s) of type '${type}', but the ${boundtype} for that type is ${bound}`;
|
|
177
|
+
super({ reason });
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
const assertFilesMeetConfig = (files, routeConfig) => Effect.gen(function* () {
|
|
181
|
+
const counts = {};
|
|
182
|
+
for (const file of files) {
|
|
183
|
+
const type = yield* matchFileType(file, objectKeys(routeConfig));
|
|
184
|
+
counts[type] = (counts[type] ?? 0) + 1;
|
|
185
|
+
const sizeLimit = routeConfig[type]?.maxFileSize;
|
|
186
|
+
if (!sizeLimit) return yield* new InvalidRouteConfigError(type, "maxFileSize");
|
|
187
|
+
const sizeLimitBytes = yield* fileSizeToBytes(sizeLimit);
|
|
188
|
+
if (file.size > sizeLimitBytes) return yield* new FileSizeMismatch(type, sizeLimit, file.size);
|
|
189
|
+
}
|
|
190
|
+
for (const _key in counts) {
|
|
191
|
+
const key = _key;
|
|
192
|
+
const config = routeConfig[key];
|
|
193
|
+
if (!config) return yield* new InvalidRouteConfigError(key);
|
|
194
|
+
const count = counts[key];
|
|
195
|
+
const min = config.minFileCount;
|
|
196
|
+
const max = config.maxFileCount;
|
|
197
|
+
if (min > max) return yield* new UploadThingError({
|
|
198
|
+
code: "BAD_REQUEST",
|
|
199
|
+
message: "Invalid config during file count - minFileCount > maxFileCount",
|
|
200
|
+
cause: `minFileCount must be less than maxFileCount for key ${key}. got: ${min} > ${max}`
|
|
201
|
+
});
|
|
202
|
+
if (count != null && count < min) return yield* new FileCountMismatch(key, "minimum", min, count);
|
|
203
|
+
if (count != null && count > max) return yield* new FileCountMismatch(key, "maximum", max, count);
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
});
|
|
207
|
+
const extractRouterConfig = (router) => Effect.forEach(objectKeys(router), (slug) => Effect.map(fillInputRouteConfig(router[slug].routerConfig), (config) => ({
|
|
208
|
+
slug,
|
|
209
|
+
config
|
|
210
|
+
})));
|
|
211
|
+
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region src/_internal/runtime.ts
|
|
214
|
+
const makeRuntime = (fetch, config) => {
|
|
215
|
+
const fetchHttpClient = Layer.provideMerge(FetchHttpClient.layer, Layer.succeed(FetchHttpClient.Fetch, fetch));
|
|
216
|
+
const withRedactedHeaders = Layer.effectDiscard(FiberRef.update(Headers.currentRedactedNames, (_) => _.concat(["x-uploadthing-api-key"])));
|
|
217
|
+
const layer = Layer.provide(Layer.mergeAll(withLogFormat, withMinimalLogLevel, fetchHttpClient, withRedactedHeaders), Layer.setConfigProvider(configProvider(config)));
|
|
218
|
+
return ManagedRuntime.make(layer);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
//#endregion
|
|
222
|
+
//#region src/_internal/types.ts
|
|
223
|
+
/**
|
|
224
|
+
* Marker used to select the region based on the incoming request
|
|
225
|
+
*/
|
|
226
|
+
const UTRegion = Symbol("uploadthing-region-symbol");
|
|
227
|
+
/**
|
|
228
|
+
* Marker used to append a `customId` to the incoming file data in `.middleware()`
|
|
229
|
+
* @example
|
|
230
|
+
* ```ts
|
|
231
|
+
* .middleware((opts) => {
|
|
232
|
+
* return {
|
|
233
|
+
* [UTFiles]: opts.files.map((file) => ({
|
|
234
|
+
* ...file,
|
|
235
|
+
* customId: generateId(),
|
|
236
|
+
* }))
|
|
237
|
+
* };
|
|
238
|
+
* })
|
|
239
|
+
* ```
|
|
240
|
+
*/
|
|
241
|
+
const UTFiles = Symbol("uploadthing-custom-id-symbol");
|
|
242
|
+
|
|
243
|
+
//#endregion
|
|
244
|
+
//#region src/_internal/handler.ts
|
|
245
|
+
var AdapterArguments = class extends Context.Tag("uploadthing/AdapterArguments")() {};
|
|
246
|
+
/**
|
|
247
|
+
* Create a request handler adapter for any framework or server library.
|
|
248
|
+
* Refer to the existing adapters for examples on how to use this function.
|
|
249
|
+
* @public
|
|
250
|
+
*
|
|
251
|
+
* @param makeAdapterArgs - Function that takes the args from your framework and returns an Effect that resolves to the adapter args.
|
|
252
|
+
* These args are passed to the `.middleware`, `.onUploadComplete`, and `.onUploadError` hooks.
|
|
253
|
+
* @param toRequest - Function that takes the args from your framework and returns an Effect that resolves to a web Request object.
|
|
254
|
+
* @param opts - The router config and other options that are normally passed to `createRequestHandler` of official adapters
|
|
255
|
+
* @param beAdapter - [Optional] The adapter name of the adapter, used for telemetry purposes
|
|
256
|
+
* @returns A function that takes the args from your framework and returns a promise that resolves to a Response object.
|
|
257
|
+
*/
|
|
258
|
+
const makeAdapterHandler = (makeAdapterArgs, toRequest, opts, beAdapter) => {
|
|
259
|
+
const managed = makeRuntime(opts.config?.fetch, opts.config);
|
|
260
|
+
const handle = Effect.promise(() => managed.runtime().then(HttpApp.toWebHandlerRuntime));
|
|
261
|
+
const app = (...args) => Effect.map(Effect.promise(() => managed.runPromise(createRequestHandler(opts, beAdapter ?? "custom"))), Effect.provideServiceEffect(AdapterArguments, makeAdapterArgs(...args)));
|
|
262
|
+
return async (...args) => {
|
|
263
|
+
const result = await handle.pipe(Effect.ap(app(...args)), Effect.ap(toRequest(...args)), Effect.withLogSpan("requestHandler"), managed.runPromise);
|
|
264
|
+
return result;
|
|
265
|
+
};
|
|
266
|
+
};
|
|
267
|
+
const createRequestHandler = (opts, beAdapter) => Effect.gen(function* () {
|
|
268
|
+
const isDevelopment = yield* IsDevelopment;
|
|
269
|
+
const routerConfig = yield* extractRouterConfig(opts.router);
|
|
270
|
+
const handleDaemon = (() => {
|
|
271
|
+
if (opts.config?.handleDaemonPromise) return opts.config.handleDaemonPromise;
|
|
272
|
+
return isDevelopment ? "void" : "await";
|
|
273
|
+
})();
|
|
274
|
+
if (isDevelopment && handleDaemon === "await") return yield* new UploadThingError({
|
|
275
|
+
code: "INVALID_SERVER_CONFIG",
|
|
276
|
+
message: "handleDaemonPromise: \"await\" is forbidden in development."
|
|
277
|
+
});
|
|
278
|
+
const GET = Effect.gen(function* () {
|
|
279
|
+
return yield* HttpServerResponse.json(routerConfig);
|
|
280
|
+
});
|
|
281
|
+
const POST = Effect.gen(function* () {
|
|
282
|
+
const { "uploadthing-hook": uploadthingHook, "x-uploadthing-package": fePackage, "x-uploadthing-version": clientVersion } = yield* HttpServerRequest.schemaHeaders(S.Struct({
|
|
283
|
+
"uploadthing-hook": UploadThingHook.pipe(S.optional),
|
|
284
|
+
"x-uploadthing-package": S.String.pipe(S.optionalWith({ default: () => "unknown" })),
|
|
285
|
+
"x-uploadthing-version": S.String.pipe(S.optionalWith({ default: () => version }))
|
|
286
|
+
}));
|
|
287
|
+
if (clientVersion !== version) {
|
|
288
|
+
const serverVersion = version;
|
|
289
|
+
yield* Effect.logWarning("Client version mismatch. Things may not work as expected, please sync your versions to ensure compatibility.").pipe(Effect.annotateLogs({
|
|
290
|
+
clientVersion,
|
|
291
|
+
serverVersion
|
|
292
|
+
}));
|
|
293
|
+
}
|
|
294
|
+
const { slug, actionType } = yield* HttpRouter.schemaParams(S.Struct({
|
|
295
|
+
actionType: ActionType.pipe(S.optional),
|
|
296
|
+
slug: S.String
|
|
297
|
+
}));
|
|
298
|
+
const uploadable = opts.router[slug];
|
|
299
|
+
if (!uploadable) {
|
|
300
|
+
const msg = `No file route found for slug ${slug}`;
|
|
301
|
+
yield* Effect.logError(msg);
|
|
302
|
+
return yield* new UploadThingError({
|
|
303
|
+
code: "NOT_FOUND",
|
|
304
|
+
message: msg
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
const { body, fiber } = yield* Match.value({
|
|
308
|
+
actionType,
|
|
309
|
+
uploadthingHook
|
|
310
|
+
}).pipe(Match.when({
|
|
311
|
+
actionType: "upload",
|
|
312
|
+
uploadthingHook: void 0
|
|
313
|
+
}, () => handleUploadAction({
|
|
314
|
+
uploadable,
|
|
315
|
+
fePackage,
|
|
316
|
+
beAdapter,
|
|
317
|
+
slug
|
|
318
|
+
})), Match.when({
|
|
319
|
+
actionType: void 0,
|
|
320
|
+
uploadthingHook: "callback"
|
|
321
|
+
}, () => handleCallbackRequest({
|
|
322
|
+
uploadable,
|
|
323
|
+
fePackage,
|
|
324
|
+
beAdapter
|
|
325
|
+
})), Match.when({
|
|
326
|
+
actionType: void 0,
|
|
327
|
+
uploadthingHook: "error"
|
|
328
|
+
}, () => handleErrorRequest({ uploadable })), Match.orElse(() => Effect.succeed({
|
|
329
|
+
body: null,
|
|
330
|
+
fiber: null
|
|
331
|
+
})));
|
|
332
|
+
if (fiber) {
|
|
333
|
+
yield* Effect.logDebug("Running fiber as daemon").pipe(Effect.annotateLogs("handleDaemon", handleDaemon));
|
|
334
|
+
if (handleDaemon === "void") {} else if (handleDaemon === "await") yield* fiber.await;
|
|
335
|
+
else if (typeof handleDaemon === "function") handleDaemon(Effect.runPromise(fiber.await));
|
|
336
|
+
}
|
|
337
|
+
yield* Effect.logDebug("Sending response").pipe(Effect.annotateLogs("body", body));
|
|
338
|
+
return yield* HttpServerResponse.json(body);
|
|
339
|
+
}).pipe(Effect.catchTags({
|
|
340
|
+
ParseError: (e) => HttpServerResponse.json(formatError(new UploadThingError({
|
|
341
|
+
code: "BAD_REQUEST",
|
|
342
|
+
message: "Invalid input",
|
|
343
|
+
cause: e.message
|
|
344
|
+
}), opts.router), { status: 400 }),
|
|
345
|
+
UploadThingError: (e) => HttpServerResponse.json(formatError(e, opts.router), { status: getStatusCodeFromError(e) })
|
|
346
|
+
}));
|
|
347
|
+
const appendResponseHeaders = Effect.map(HttpServerResponse.setHeader("x-uploadthing-version", version));
|
|
348
|
+
return HttpRouter.empty.pipe(HttpRouter.get("*", GET), HttpRouter.post("*", POST), HttpRouter.use(appendResponseHeaders));
|
|
349
|
+
}).pipe(Effect.withLogSpan("createRequestHandler"));
|
|
350
|
+
const handleErrorRequest = (opts) => Effect.gen(function* () {
|
|
351
|
+
const { uploadable } = opts;
|
|
352
|
+
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
353
|
+
const { apiKey } = yield* UTToken;
|
|
354
|
+
const verified = yield* verifySignature(yield* request.text, request.headers["x-uploadthing-signature"] ?? null, apiKey);
|
|
355
|
+
yield* Effect.logDebug(`Signature verified: ${verified}`);
|
|
356
|
+
if (!verified) {
|
|
357
|
+
yield* Effect.logError("Invalid signature");
|
|
358
|
+
return yield* new UploadThingError({
|
|
359
|
+
code: "BAD_REQUEST",
|
|
360
|
+
message: "Invalid signature"
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
const requestInput = yield* HttpServerRequest.schemaBodyJson(S.Struct({
|
|
364
|
+
fileKey: S.String,
|
|
365
|
+
error: S.String
|
|
366
|
+
}));
|
|
367
|
+
yield* Effect.logDebug("Handling error callback request with input:").pipe(Effect.annotateLogs("json", requestInput));
|
|
368
|
+
const adapterArgs = yield* AdapterArguments;
|
|
369
|
+
const fiber = yield* Effect.tryPromise({
|
|
370
|
+
try: async () => uploadable.onUploadError({
|
|
371
|
+
...adapterArgs,
|
|
372
|
+
error: new UploadThingError({
|
|
373
|
+
code: "UPLOAD_FAILED",
|
|
374
|
+
message: `Upload failed for ${requestInput.fileKey}: ${requestInput.error}`
|
|
375
|
+
}),
|
|
376
|
+
fileKey: requestInput.fileKey
|
|
377
|
+
}),
|
|
378
|
+
catch: (error) => new UploadThingError({
|
|
379
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
380
|
+
message: "Failed to run onUploadError",
|
|
381
|
+
cause: error
|
|
382
|
+
})
|
|
383
|
+
}).pipe(Effect.tapError((error) => Effect.logError("Failed to run onUploadError. You probably shouldn't be throwing errors here.").pipe(Effect.annotateLogs("error", error)))).pipe(Effect.ignoreLogged, Effect.forkDaemon);
|
|
384
|
+
return {
|
|
385
|
+
body: null,
|
|
386
|
+
fiber
|
|
387
|
+
};
|
|
388
|
+
}).pipe(Effect.withLogSpan("handleErrorRequest"));
|
|
389
|
+
const handleCallbackRequest = (opts) => Effect.gen(function* () {
|
|
390
|
+
const { uploadable, fePackage, beAdapter } = opts;
|
|
391
|
+
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
392
|
+
const { apiKey } = yield* UTToken;
|
|
393
|
+
const verified = yield* verifySignature(yield* request.text, request.headers["x-uploadthing-signature"] ?? null, apiKey);
|
|
394
|
+
yield* Effect.logDebug(`Signature verified: ${verified}`);
|
|
395
|
+
if (!verified) {
|
|
396
|
+
yield* Effect.logError("Invalid signature");
|
|
397
|
+
return yield* new UploadThingError({
|
|
398
|
+
code: "BAD_REQUEST",
|
|
399
|
+
message: "Invalid signature"
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
const requestInput = yield* HttpServerRequest.schemaBodyJson(S.Struct({
|
|
403
|
+
status: S.String,
|
|
404
|
+
file: UploadedFileData,
|
|
405
|
+
origin: S.String,
|
|
406
|
+
metadata: S.Record({
|
|
407
|
+
key: S.String,
|
|
408
|
+
value: S.Unknown
|
|
409
|
+
})
|
|
410
|
+
}));
|
|
411
|
+
yield* Effect.logDebug("Handling callback request with input:").pipe(Effect.annotateLogs("json", requestInput));
|
|
412
|
+
/**
|
|
413
|
+
* Run `.onUploadComplete` as a daemon to prevent the
|
|
414
|
+
* request from UT to potentially timeout.
|
|
415
|
+
*/
|
|
416
|
+
const fiber = yield* Effect.gen(function* () {
|
|
417
|
+
const adapterArgs = yield* AdapterArguments;
|
|
418
|
+
const serverData = yield* Effect.tryPromise({
|
|
419
|
+
try: async () => uploadable.onUploadComplete({
|
|
420
|
+
...adapterArgs,
|
|
421
|
+
file: {
|
|
422
|
+
...requestInput.file,
|
|
423
|
+
get url() {
|
|
424
|
+
logDeprecationWarning("`file.url` is deprecated and will be removed in uploadthing v9. Use `file.ufsUrl` instead.");
|
|
425
|
+
return requestInput.file.url;
|
|
426
|
+
},
|
|
427
|
+
get appUrl() {
|
|
428
|
+
logDeprecationWarning("`file.appUrl` is deprecated and will be removed in uploadthing v9. Use `file.ufsUrl` instead.");
|
|
429
|
+
return requestInput.file.appUrl;
|
|
430
|
+
}
|
|
431
|
+
},
|
|
432
|
+
metadata: requestInput.metadata
|
|
433
|
+
}),
|
|
434
|
+
catch: (error) => new UploadThingError({
|
|
435
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
436
|
+
message: "Failed to run onUploadComplete. You probably shouldn't be throwing errors here.",
|
|
437
|
+
cause: error
|
|
438
|
+
})
|
|
439
|
+
});
|
|
440
|
+
const payload = {
|
|
441
|
+
fileKey: requestInput.file.key,
|
|
442
|
+
callbackData: serverData ?? null
|
|
443
|
+
};
|
|
444
|
+
yield* Effect.logDebug("'onUploadComplete' callback finished. Sending response to UploadThing:").pipe(Effect.annotateLogs("callbackData", payload));
|
|
445
|
+
const httpClient = (yield* HttpClient.HttpClient).pipe(HttpClient.filterStatusOk);
|
|
446
|
+
yield* HttpClientRequest.post(`/callback-result`).pipe(HttpClientRequest.prependUrl(requestInput.origin), HttpClientRequest.setHeaders({
|
|
447
|
+
"x-uploadthing-api-key": Redacted.value(apiKey),
|
|
448
|
+
"x-uploadthing-version": version,
|
|
449
|
+
"x-uploadthing-be-adapter": beAdapter,
|
|
450
|
+
"x-uploadthing-fe-package": fePackage
|
|
451
|
+
}), HttpClientRequest.bodyJson(payload), Effect.flatMap(httpClient.execute), Effect.tapError(logHttpClientError("Failed to register callback result")), Effect.flatMap(HttpClientResponse.schemaBodyJson(CallbackResultResponse)), Effect.tap(Effect.log("Sent callback result to UploadThing")), Effect.scoped);
|
|
452
|
+
}).pipe(Effect.ignoreLogged, Effect.forkDaemon);
|
|
453
|
+
return {
|
|
454
|
+
body: null,
|
|
455
|
+
fiber
|
|
456
|
+
};
|
|
457
|
+
}).pipe(Effect.withLogSpan("handleCallbackRequest"));
|
|
458
|
+
const runRouteMiddleware = (opts) => Effect.gen(function* () {
|
|
459
|
+
const { json: { files, input }, uploadable } = opts;
|
|
460
|
+
yield* Effect.logDebug("Running middleware");
|
|
461
|
+
const adapterArgs = yield* AdapterArguments;
|
|
462
|
+
const metadata = yield* Effect.tryPromise({
|
|
463
|
+
try: async () => uploadable.middleware({
|
|
464
|
+
...adapterArgs,
|
|
465
|
+
input,
|
|
466
|
+
files
|
|
467
|
+
}),
|
|
468
|
+
catch: (error) => error instanceof UploadThingError ? error : new UploadThingError({
|
|
469
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
470
|
+
message: "Failed to run middleware",
|
|
471
|
+
cause: error
|
|
472
|
+
})
|
|
473
|
+
});
|
|
474
|
+
if (metadata[UTFiles] && metadata[UTFiles].length !== files.length) {
|
|
475
|
+
const msg = `Expected files override to have the same length as original files, got ${metadata[UTFiles].length} but expected ${files.length}`;
|
|
476
|
+
yield* Effect.logError(msg);
|
|
477
|
+
return yield* new UploadThingError({
|
|
478
|
+
code: "BAD_REQUEST",
|
|
479
|
+
message: "Files override must have the same length as files",
|
|
480
|
+
cause: msg
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
const filesWithCustomIds = yield* Effect.forEach(files, (file, idx) => Effect.gen(function* () {
|
|
484
|
+
const theirs = metadata[UTFiles]?.[idx];
|
|
485
|
+
if (theirs && theirs.size !== file.size) yield* Effect.logWarning("File size mismatch. Reverting to original size");
|
|
486
|
+
return {
|
|
487
|
+
name: theirs?.name ?? file.name,
|
|
488
|
+
size: file.size,
|
|
489
|
+
type: file.type,
|
|
490
|
+
customId: theirs?.customId,
|
|
491
|
+
lastModified: theirs?.lastModified ?? Date.now()
|
|
492
|
+
};
|
|
493
|
+
}));
|
|
494
|
+
return {
|
|
495
|
+
metadata,
|
|
496
|
+
filesWithCustomIds,
|
|
497
|
+
preferredRegion: metadata[UTRegion]
|
|
498
|
+
};
|
|
499
|
+
}).pipe(Effect.withLogSpan("runRouteMiddleware"));
|
|
500
|
+
const handleUploadAction = (opts) => Effect.gen(function* () {
|
|
501
|
+
const httpClient = (yield* HttpClient.HttpClient).pipe(HttpClient.filterStatusOk);
|
|
502
|
+
const { uploadable, fePackage, beAdapter, slug } = opts;
|
|
503
|
+
const json = yield* HttpServerRequest.schemaBodyJson(UploadActionPayload);
|
|
504
|
+
yield* Effect.logDebug("Handling upload request").pipe(Effect.annotateLogs("json", json));
|
|
505
|
+
yield* Effect.logDebug("Parsing user input");
|
|
506
|
+
const parsedInput = yield* Effect.tryPromise({
|
|
507
|
+
try: () => getParseFn(uploadable.inputParser)(json.input),
|
|
508
|
+
catch: (error) => new UploadThingError({
|
|
509
|
+
code: "BAD_REQUEST",
|
|
510
|
+
message: "Invalid input",
|
|
511
|
+
cause: error
|
|
512
|
+
})
|
|
513
|
+
});
|
|
514
|
+
yield* Effect.logDebug("Input parsed successfully").pipe(Effect.annotateLogs("input", parsedInput));
|
|
515
|
+
const { metadata, filesWithCustomIds, preferredRegion } = yield* runRouteMiddleware({
|
|
516
|
+
json: {
|
|
517
|
+
input: parsedInput,
|
|
518
|
+
files: json.files
|
|
519
|
+
},
|
|
520
|
+
uploadable
|
|
521
|
+
});
|
|
522
|
+
yield* Effect.logDebug("Parsing route config").pipe(Effect.annotateLogs("routerConfig", uploadable.routerConfig));
|
|
523
|
+
const parsedConfig = yield* fillInputRouteConfig(uploadable.routerConfig).pipe(Effect.catchTag("InvalidRouteConfig", (err) => new UploadThingError({
|
|
524
|
+
code: "BAD_REQUEST",
|
|
525
|
+
message: "Invalid route config",
|
|
526
|
+
cause: err
|
|
527
|
+
})));
|
|
528
|
+
yield* Effect.logDebug("Route config parsed successfully").pipe(Effect.annotateLogs("routeConfig", parsedConfig));
|
|
529
|
+
yield* Effect.logDebug("Validating files meet the config requirements").pipe(Effect.annotateLogs("files", json.files));
|
|
530
|
+
yield* assertFilesMeetConfig(json.files, parsedConfig).pipe(Effect.mapError((e) => new UploadThingError({
|
|
531
|
+
code: "BAD_REQUEST",
|
|
532
|
+
message: `Invalid config: ${e._tag}`,
|
|
533
|
+
cause: "reason" in e ? e.reason : e.message
|
|
534
|
+
})));
|
|
535
|
+
yield* Effect.logDebug("Files validated.");
|
|
536
|
+
const fileUploadRequests = yield* Effect.forEach(filesWithCustomIds, (file) => Effect.map(matchFileType(file, objectKeys(parsedConfig)), (type) => ({
|
|
537
|
+
name: file.name,
|
|
538
|
+
size: file.size,
|
|
539
|
+
type: file.type || type,
|
|
540
|
+
lastModified: file.lastModified,
|
|
541
|
+
customId: file.customId,
|
|
542
|
+
contentDisposition: parsedConfig[type]?.contentDisposition ?? "inline",
|
|
543
|
+
acl: parsedConfig[type]?.acl
|
|
544
|
+
}))).pipe(Effect.catchTags({
|
|
545
|
+
InvalidFileType: (e) => Effect.die(e),
|
|
546
|
+
UnknownFileType: (e) => Effect.die(e)
|
|
547
|
+
}));
|
|
548
|
+
const routeOptions = uploadable.routeOptions;
|
|
549
|
+
const { apiKey, appId } = yield* UTToken;
|
|
550
|
+
const ingestUrl = yield* IngestUrl(preferredRegion);
|
|
551
|
+
const isDev = yield* IsDevelopment;
|
|
552
|
+
yield* Effect.logDebug("Generating presigned URLs").pipe(Effect.annotateLogs("fileUploadRequests", fileUploadRequests), Effect.annotateLogs("ingestUrl", ingestUrl));
|
|
553
|
+
const presignedUrls = yield* Effect.forEach(fileUploadRequests, (file) => Effect.gen(function* () {
|
|
554
|
+
const key = yield* generateKey(file, appId, routeOptions.getFileHashParts);
|
|
555
|
+
const url = yield* generateSignedURL(`${ingestUrl}/${key}`, apiKey, {
|
|
556
|
+
ttlInSeconds: routeOptions.presignedURLTTL,
|
|
557
|
+
data: {
|
|
558
|
+
"x-ut-identifier": appId,
|
|
559
|
+
"x-ut-file-name": file.name,
|
|
560
|
+
"x-ut-file-size": file.size,
|
|
561
|
+
"x-ut-file-type": file.type,
|
|
562
|
+
"x-ut-slug": slug,
|
|
563
|
+
"x-ut-custom-id": file.customId,
|
|
564
|
+
"x-ut-content-disposition": file.contentDisposition,
|
|
565
|
+
"x-ut-acl": file.acl
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
return {
|
|
569
|
+
url,
|
|
570
|
+
key
|
|
571
|
+
};
|
|
572
|
+
}), { concurrency: "unbounded" });
|
|
573
|
+
const serverReq = yield* HttpServerRequest.HttpServerRequest;
|
|
574
|
+
const requestUrl = yield* HttpServerRequest.toURL(serverReq);
|
|
575
|
+
const devHookRequest = yield* Config.string("callbackUrl").pipe(Config.withDefault(requestUrl.origin + requestUrl.pathname), Effect.map((url) => HttpClientRequest.post(url).pipe(HttpClientRequest.appendUrlParam("slug", slug))));
|
|
576
|
+
const metadataRequest = HttpClientRequest.post("/route-metadata").pipe(HttpClientRequest.prependUrl(ingestUrl), HttpClientRequest.setHeaders({
|
|
577
|
+
"x-uploadthing-api-key": Redacted.value(apiKey),
|
|
578
|
+
"x-uploadthing-version": version,
|
|
579
|
+
"x-uploadthing-be-adapter": beAdapter,
|
|
580
|
+
"x-uploadthing-fe-package": fePackage
|
|
581
|
+
}), HttpClientRequest.bodyJson({
|
|
582
|
+
fileKeys: presignedUrls.map(({ key }) => key),
|
|
583
|
+
metadata,
|
|
584
|
+
isDev,
|
|
585
|
+
callbackUrl: devHookRequest.url,
|
|
586
|
+
callbackSlug: slug,
|
|
587
|
+
awaitServerData: routeOptions.awaitServerData ?? true
|
|
588
|
+
}), Effect.flatMap(httpClient.execute));
|
|
589
|
+
const handleDevStreamError = Effect.fn("handleDevStreamError")(function* (err, chunk) {
|
|
590
|
+
const schema = S.parseJson(S.Struct({ file: UploadedFileData }));
|
|
591
|
+
const parsedChunk = yield* S.decodeUnknown(schema)(chunk);
|
|
592
|
+
const key = parsedChunk.file.key;
|
|
593
|
+
yield* Effect.logError("Failed to forward callback request from dev stream").pipe(Effect.annotateLogs({
|
|
594
|
+
fileKey: key,
|
|
595
|
+
error: err.message
|
|
596
|
+
}));
|
|
597
|
+
const httpResponse = yield* HttpClientRequest.post("/callback-result").pipe(HttpClientRequest.prependUrl(ingestUrl), HttpClientRequest.setHeaders({
|
|
598
|
+
"x-uploadthing-api-key": Redacted.value(apiKey),
|
|
599
|
+
"x-uploadthing-version": version,
|
|
600
|
+
"x-uploadthing-be-adapter": beAdapter,
|
|
601
|
+
"x-uploadthing-fe-package": fePackage
|
|
602
|
+
}), HttpClientRequest.bodyJson({
|
|
603
|
+
fileKey: key,
|
|
604
|
+
error: `Failed to forward callback request from dev stream: ${err.message}`
|
|
605
|
+
}), Effect.flatMap(httpClient.execute));
|
|
606
|
+
yield* logHttpClientResponse("Reported callback error to UploadThing")(httpResponse);
|
|
607
|
+
});
|
|
608
|
+
const fiber = yield* Effect.if(isDev, {
|
|
609
|
+
onTrue: () => metadataRequest.pipe(Effect.tapBoth({
|
|
610
|
+
onSuccess: logHttpClientResponse("Registered metadata", { mixin: "None" }),
|
|
611
|
+
onFailure: logHttpClientError("Failed to register metadata")
|
|
612
|
+
}), HttpClientResponse.stream, handleJsonLineStream(MetadataFetchStreamPart, (chunk) => devHookRequest.pipe(HttpClientRequest.setHeaders({
|
|
613
|
+
"uploadthing-hook": chunk.hook,
|
|
614
|
+
"x-uploadthing-signature": chunk.signature
|
|
615
|
+
}), HttpClientRequest.setBody(HttpBody.text(chunk.payload, "application/json")), httpClient.execute, Effect.tap(logHttpClientResponse("Successfully forwarded callback request from dev stream")), Effect.catchTag("ResponseError", (err) => handleDevStreamError(err, chunk.payload)), Effect.annotateLogs(chunk), Effect.asVoid, Effect.ignoreLogged, Effect.scoped))),
|
|
616
|
+
onFalse: () => metadataRequest.pipe(Effect.tapBoth({
|
|
617
|
+
onSuccess: logHttpClientResponse("Registered metadata"),
|
|
618
|
+
onFailure: logHttpClientError("Failed to register metadata")
|
|
619
|
+
}), Effect.flatMap(HttpClientResponse.schemaBodyJson(MetadataFetchResponse)), Effect.scoped)
|
|
620
|
+
}).pipe(Effect.forkDaemon);
|
|
621
|
+
const presigneds = presignedUrls.map((p, i) => ({
|
|
622
|
+
url: p.url,
|
|
623
|
+
key: p.key,
|
|
624
|
+
name: fileUploadRequests[i].name,
|
|
625
|
+
customId: fileUploadRequests[i].customId ?? null
|
|
626
|
+
}));
|
|
627
|
+
yield* Effect.logInfo("Sending presigned URLs to client").pipe(Effect.annotateLogs("presignedUrls", presigneds));
|
|
628
|
+
return {
|
|
629
|
+
body: presigneds,
|
|
630
|
+
fiber
|
|
631
|
+
};
|
|
632
|
+
}).pipe(Effect.withLogSpan("handleUploadAction"));
|
|
633
|
+
|
|
634
|
+
//#endregion
|
|
635
|
+
//#region src/_internal/upload-builder.ts
|
|
636
|
+
function internalCreateBuilder(initDef = {}) {
|
|
637
|
+
const _def = {
|
|
638
|
+
$types: {},
|
|
639
|
+
routerConfig: { image: { maxFileSize: "4MB" } },
|
|
640
|
+
routeOptions: { awaitServerData: true },
|
|
641
|
+
inputParser: {
|
|
642
|
+
parseAsync: () => Promise.resolve(void 0),
|
|
643
|
+
_input: void 0,
|
|
644
|
+
_output: void 0
|
|
645
|
+
},
|
|
646
|
+
middleware: () => ({}),
|
|
647
|
+
onUploadError: () => {},
|
|
648
|
+
onUploadComplete: () => void 0,
|
|
649
|
+
errorFormatter: initDef.errorFormatter ?? defaultErrorFormatter,
|
|
650
|
+
...initDef
|
|
651
|
+
};
|
|
652
|
+
return {
|
|
653
|
+
input(userParser) {
|
|
654
|
+
return internalCreateBuilder({
|
|
655
|
+
..._def,
|
|
656
|
+
inputParser: userParser
|
|
657
|
+
});
|
|
658
|
+
},
|
|
659
|
+
middleware(userMiddleware) {
|
|
660
|
+
return internalCreateBuilder({
|
|
661
|
+
..._def,
|
|
662
|
+
middleware: userMiddleware
|
|
663
|
+
});
|
|
664
|
+
},
|
|
665
|
+
onUploadComplete(userUploadComplete) {
|
|
666
|
+
return {
|
|
667
|
+
..._def,
|
|
668
|
+
onUploadComplete: userUploadComplete
|
|
669
|
+
};
|
|
670
|
+
},
|
|
671
|
+
onUploadError(userOnUploadError) {
|
|
672
|
+
return internalCreateBuilder({
|
|
673
|
+
..._def,
|
|
674
|
+
onUploadError: userOnUploadError
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Create a builder for your backend adapter.
|
|
681
|
+
* Refer to the existing adapters for examples on how to use this function.
|
|
682
|
+
* @public
|
|
683
|
+
*
|
|
684
|
+
* @param opts - Options for the builder
|
|
685
|
+
* @returns A file route builder for making UploadThing file routes
|
|
686
|
+
*/
|
|
687
|
+
function createBuilder(opts) {
|
|
688
|
+
return (input, config) => {
|
|
689
|
+
return internalCreateBuilder({
|
|
690
|
+
routerConfig: input,
|
|
691
|
+
routeOptions: config ?? {},
|
|
692
|
+
...opts
|
|
693
|
+
});
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
//#endregion
|
|
698
|
+
export { AdapterArguments, ApiUrl, IngestUrl, UTFiles, UTRegion, UTToken, UfsAppIdLocation, UfsHost, configProvider, createBuilder, createRequestHandler, extractRouterConfig, logHttpClientError, logHttpClientResponse, makeAdapterHandler, makeRuntime };
|
|
699
|
+
//# sourceMappingURL=upload-builder-BlFOAnsv.js.map
|