@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.
Files changed (113) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +33 -0
  3. package/changes.json +18 -0
  4. package/client/index.cjs +331 -0
  5. package/client/index.d.cts +36 -0
  6. package/client/index.d.cts.map +1 -0
  7. package/client/index.d.ts +36 -0
  8. package/client/index.d.ts.map +1 -0
  9. package/client/index.js +286 -0
  10. package/client/index.js.map +1 -0
  11. package/client-future/index.cjs +426 -0
  12. package/client-future/index.d.cts +373 -0
  13. package/client-future/index.d.cts.map +1 -0
  14. package/client-future/index.d.ts +373 -0
  15. package/client-future/index.d.ts.map +1 -0
  16. package/client-future/index.js +383 -0
  17. package/client-future/index.js.map +1 -0
  18. package/dist/chunk-CUT6urMc.cjs +30 -0
  19. package/dist/deprecations-DPGpmqha.cjs +13 -0
  20. package/dist/deprecations-pLmw6Ytd.js +8 -0
  21. package/dist/deprecations-pLmw6Ytd.js.map +1 -0
  22. package/dist/package-BQ_k22T9.cjs +11 -0
  23. package/dist/package-DpScpvTA.js +6 -0
  24. package/dist/package-DpScpvTA.js.map +1 -0
  25. package/dist/shared-schemas-BmG5ARoX.js +82 -0
  26. package/dist/shared-schemas-BmG5ARoX.js.map +1 -0
  27. package/dist/shared-schemas-CG9VaBtT.cjs +129 -0
  28. package/dist/to-web-request-BQtxSXgE.cjs +98 -0
  29. package/dist/to-web-request-DhP0wXG-.js +87 -0
  30. package/dist/to-web-request-DhP0wXG-.js.map +1 -0
  31. package/dist/types-Bs3w2d_3.d.ts +627 -0
  32. package/dist/types-Bs3w2d_3.d.ts.map +1 -0
  33. package/dist/types-DiVC1t2V.d.cts +625 -0
  34. package/dist/types-DiVC1t2V.d.cts.map +1 -0
  35. package/dist/upload-builder-BUa7tovh.d.cts +32 -0
  36. package/dist/upload-builder-BUa7tovh.d.cts.map +1 -0
  37. package/dist/upload-builder-BcFawEj0.d.ts +32 -0
  38. package/dist/upload-builder-BcFawEj0.d.ts.map +1 -0
  39. package/dist/upload-builder-BlFOAnsv.js +699 -0
  40. package/dist/upload-builder-BlFOAnsv.js.map +1 -0
  41. package/dist/upload-builder-D6Ken9H0.cjs +794 -0
  42. package/dist/ut-reporter-BHoyNnzW.cjs +120 -0
  43. package/dist/ut-reporter-Dlppchbx.js +103 -0
  44. package/dist/ut-reporter-Dlppchbx.js.map +1 -0
  45. package/effect-platform/index.cjs +22 -0
  46. package/effect-platform/index.d.cts +54 -0
  47. package/effect-platform/index.d.cts.map +1 -0
  48. package/effect-platform/index.d.ts +54 -0
  49. package/effect-platform/index.d.ts.map +1 -0
  50. package/effect-platform/index.js +19 -0
  51. package/effect-platform/index.js.map +1 -0
  52. package/express/index.cjs +30 -0
  53. package/express/index.d.cts +28 -0
  54. package/express/index.d.cts.map +1 -0
  55. package/express/index.d.ts +28 -0
  56. package/express/index.d.ts.map +1 -0
  57. package/express/index.js +27 -0
  58. package/express/index.js.map +1 -0
  59. package/fastify/index.cjs +27 -0
  60. package/fastify/index.d.cts +28 -0
  61. package/fastify/index.d.cts.map +1 -0
  62. package/fastify/index.d.ts +28 -0
  63. package/fastify/index.d.ts.map +1 -0
  64. package/fastify/index.js +24 -0
  65. package/fastify/index.js.map +1 -0
  66. package/h3/index.cjs +20 -0
  67. package/h3/index.d.cts +28 -0
  68. package/h3/index.d.cts.map +1 -0
  69. package/h3/index.d.ts +28 -0
  70. package/h3/index.d.ts.map +1 -0
  71. package/h3/index.js +17 -0
  72. package/h3/index.js.map +1 -0
  73. package/next/index.cjs +22 -0
  74. package/next/index.d.cts +30 -0
  75. package/next/index.d.cts.map +1 -0
  76. package/next/index.d.ts +30 -0
  77. package/next/index.d.ts.map +1 -0
  78. package/next/index.js +19 -0
  79. package/next/index.js.map +1 -0
  80. package/next-legacy/index.cjs +28 -0
  81. package/next-legacy/index.d.cts +28 -0
  82. package/next-legacy/index.d.cts.map +1 -0
  83. package/next-legacy/index.d.ts +28 -0
  84. package/next-legacy/index.d.ts.map +1 -0
  85. package/next-legacy/index.js +25 -0
  86. package/next-legacy/index.js.map +1 -0
  87. package/package.json +210 -0
  88. package/remix/index.cjs +22 -0
  89. package/remix/index.d.cts +30 -0
  90. package/remix/index.d.cts.map +1 -0
  91. package/remix/index.d.ts +30 -0
  92. package/remix/index.d.ts.map +1 -0
  93. package/remix/index.js +19 -0
  94. package/remix/index.js.map +1 -0
  95. package/server/index.cjs +414 -0
  96. package/server/index.d.cts +211 -0
  97. package/server/index.d.cts.map +1 -0
  98. package/server/index.d.ts +213 -0
  99. package/server/index.d.ts.map +1 -0
  100. package/server/index.js +405 -0
  101. package/server/index.js.map +1 -0
  102. package/tw/index.cjs +70 -0
  103. package/tw/index.d.cts +29 -0
  104. package/tw/index.d.cts.map +1 -0
  105. package/tw/index.d.ts +29 -0
  106. package/tw/index.d.ts.map +1 -0
  107. package/tw/index.js +74 -0
  108. package/tw/index.js.map +1 -0
  109. package/tw/v4.css +11 -0
  110. package/types/index.cjs +3 -0
  111. package/types/index.d.cts +2 -0
  112. package/types/index.d.ts +2 -0
  113. 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