diff --git a/.changeset/odd-icons-pretend.md b/.changeset/odd-icons-pretend.md new file mode 100644 index 0000000000..bc5e221e4b --- /dev/null +++ b/.changeset/odd-icons-pretend.md @@ -0,0 +1,5 @@ +--- +"uploadthing": minor +--- + +feat: support `effect/Schema` and `@standard-schema/spec` input validators diff --git a/docs/src/app/(docs)/file-routes/page.mdx b/docs/src/app/(docs)/file-routes/page.mdx index 4b0e19f1de..7e27fae6fa 100644 --- a/docs/src/app/(docs)/file-routes/page.mdx +++ b/docs/src/app/(docs)/file-routes/page.mdx @@ -171,10 +171,30 @@ f(["image"]) ## `input` {{ since: '5.0' }} -You can pass a [Zod](https://github.com/colinhacks/zod) schema to validate user -input from the client. This data comes from the client when the upload starts. -If validation here fails, an error will be thrown and none of your `middleware` -n'or `onUploadComplete` functions will be executed. +You can pass a schema validator to validate user input from the client. This +data comes from the client when the upload starts. If validation here fails, an +error will be thrown and none of your `middleware` n'or `onUploadComplete` +functions will be executed. + +Historically, only [Zod](https://github.com/colinhacks/zod) was supported, but +since `uploadthing@7.4` the following validators are supported: + +- [Zod >=3](https://github.com/colinhacks/zod) +- [Effect/Schema >=3.10](https://effect.website/docs/schema/introduction) is + partially supported with the following limitations: + - Top-level schema must be of type `Schema.Schema` + - Must not be wrapped (e.g., no `optional` or + `optionalWith`) +- [Standard Schema specification](https://github.com/standard-schema/standard-schema), + for example [Valibot >=1.0](https://github.com/fabian-hiller/valibot) and + [ArkType >=2.0](https://github.com/arktypeio/arktype) + + + The schema's **input type** must only contain JSON serializable types as + UploadThing does no special data transforming to handle non-JSON types. You + may do transformations yourself, for example `z.string().transform(Date)` is + valid where as `z.date()` is not. + The input is validated on **your** server and only leaves your server if you pass it along from the `.middleware` to the `.onUploadComplete`. If you only use diff --git a/packages/uploadthing/package.json b/packages/uploadthing/package.json index 46b9b771d5..a8b4b8f296 100644 --- a/packages/uploadthing/package.json +++ b/packages/uploadthing/package.json @@ -151,6 +151,7 @@ }, "dependencies": { "@effect/platform": "0.69.24", + "@standard-schema/spec": "1.0.0-beta.3", "@uploadthing/mime-types": "workspace:*", "@uploadthing/shared": "workspace:*", "effect": "3.10.15" @@ -176,6 +177,7 @@ "type-fest": "^4.10.3", "typescript": "^5.5.2", "undici": "^6.6.2", + "valibot": "1.0.0-beta.7", "vue": "^3.4.21", "wait-on": "^7.2.0", "zod": "^3.23.8" diff --git a/packages/uploadthing/src/internal/handler.ts b/packages/uploadthing/src/internal/handler.ts index 23e4ff1114..727fdbaaa5 100644 --- a/packages/uploadthing/src/internal/handler.ts +++ b/packages/uploadthing/src/internal/handler.ts @@ -460,7 +460,7 @@ const handleUploadAction = (opts: { // validate the input yield* Effect.logDebug("Parsing user input"); const parsedInput = yield* Effect.tryPromise({ - try: async () => getParseFn(uploadable.inputParser)(json.input), + try: () => getParseFn(uploadable.inputParser)(json.input), catch: (error) => new UploadThingError({ code: "BAD_REQUEST", diff --git a/packages/uploadthing/src/internal/parser.ts b/packages/uploadthing/src/internal/parser.ts index ab01f22b78..caa1453c73 100644 --- a/packages/uploadthing/src/internal/parser.ts +++ b/packages/uploadthing/src/internal/parser.ts @@ -1,25 +1,71 @@ -import type { Json, MaybePromise } from "@uploadthing/shared"; +import type * as Standard from "@standard-schema/spec"; +import * as Cause from "effect/Cause"; +import * as Data from "effect/Data"; +import * as Runtime from "effect/Runtime"; +import * as Schema from "effect/Schema"; -/** - * TODO: Do we wanna support effect/schema parsers now?? - */ +import type { Json } from "@uploadthing/shared"; -// Don't want to use Zod cause it's an optional dependency -export type ParseFn = (input: unknown) => MaybePromise; -export type ParserZodEsque = { +export type ParseFn = (input: unknown) => Promise; + +export type ParserZodEsque = { _input: TInput; _output: TParsedInput; // if using .transform etc - parse: ParseFn; + parseAsync: ParseFn; }; // In case we add support for more parsers later -export type JsonParser = ParserZodEsque; +export type JsonParser = + | ParserZodEsque + | Standard.v1.StandardSchema + | Schema.Schema; + +export class ParserError extends Data.TaggedError("ParserError")<{ + cause: unknown; +}> { + message = + "Input validation failed. The original error with it's validation issues is in the error cause."; +} + +export function getParseFn< + TOut extends Json, + TParser extends JsonParser, +>(parser: TParser): ParseFn { + if ("~standard" in parser) { + /** + * Standard Schema + */ + return async (value) => { + const result = await parser["~standard"].validate(value); + if (result.issues) { + throw new ParserError({ cause: result.issues }); + } + return result.value; + }; + } + + if ("parseAsync" in parser && typeof parser.parseAsync === "function") { + /** + * Zod + * TODO (next major): Consider wrapping ZodError in ParserError + */ + return parser.parseAsync; + } -export function getParseFn( - parser: TParser, -): ParseFn { - if (typeof parser.parse === "function") { - return parser.parse; + if (Schema.isSchema(parser)) { + /** + * Effect Schema + */ + return (value) => + Schema.decodeUnknownPromise(parser as Schema.Schema)( + value, + ).catch((error) => { + throw new ParserError({ + cause: Cause.squash( + (error as Runtime.FiberFailure)[Runtime.FiberFailureCauseId], + ), + }); + }); } throw new Error("Invalid parser"); diff --git a/packages/uploadthing/src/internal/types.ts b/packages/uploadthing/src/internal/types.ts index 2a3c410611..bbbfdc0d2f 100644 --- a/packages/uploadthing/src/internal/types.ts +++ b/packages/uploadthing/src/internal/types.ts @@ -97,13 +97,13 @@ type UploadErrorFn> = ( ) => MaybePromise; export interface UploadBuilder { - input: ( + input: ( parser: TParams["_input"]["in"] extends UnsetMarker - ? TParser + ? JsonParser : ErrorMessage<"input is already set">, ) => UploadBuilder<{ _routeOptions: TParams["_routeOptions"]; - _input: { in: TParser["_input"]; out: TParser["_output"] }; + _input: { in: TIn; out: TOut }; _metadata: TParams["_metadata"]; _adapterFnArgs: TParams["_adapterFnArgs"]; _errorShape: TParams["_errorShape"]; @@ -173,7 +173,7 @@ export interface FileRoute { $types: TTypes; routerConfig: FileRouterInputConfig; routeOptions: RouteOptions; - inputParser: JsonParser; + inputParser: JsonParser; middleware: MiddlewareFn; onUploadError: UploadErrorFn; errorFormatter: (err: UploadThingError) => any; diff --git a/packages/uploadthing/src/internal/upload-builder.ts b/packages/uploadthing/src/internal/upload-builder.ts index fb85c90c49..c041e879a3 100644 --- a/packages/uploadthing/src/internal/upload-builder.ts +++ b/packages/uploadthing/src/internal/upload-builder.ts @@ -42,7 +42,7 @@ function internalCreateBuilder< }, inputParser: { - parse: () => undefined, + parseAsync: () => Promise.resolve(undefined), _input: undefined, _output: undefined, }, diff --git a/packages/uploadthing/test/input.test.ts b/packages/uploadthing/test/input.test.ts new file mode 100644 index 0000000000..d4d5f9e2d5 --- /dev/null +++ b/packages/uploadthing/test/input.test.ts @@ -0,0 +1,209 @@ +import { ParseError } from "effect/ParseResult"; +import * as Schema from "effect/Schema"; +import * as v from "valibot"; +import { expect, expectTypeOf, it } from "vitest"; +import * as z from "zod"; + +import { noop } from "@uploadthing/shared"; + +import { getParseFn, ParserError } from "../src/internal/parser"; +import { createBuilder } from "../src/internal/upload-builder"; +import type { inferEndpointInput } from "../src/types"; + +const f = createBuilder<{ req: Request; res: undefined; event: undefined }>(); + +it.each([ + ["zod", z.string()], + ["valibot", v.string()], + ["effect/schema", Schema.String], +])("primitive string schema (%s)", async (_, input) => { + const fileRoute = f(["image"]) + .input(input) + .middleware((opts) => { + expectTypeOf(opts.input); + return {}; + }) + .onUploadComplete(noop); + + type Input = inferEndpointInput; + expectTypeOf().toMatchTypeOf(); + + const parsedInput = await getParseFn(fileRoute.inputParser)("bar"); + expect(parsedInput).toEqual("bar"); +}); + +it.each([ + ["zod", z.object({ foo: z.string() })], + ["valibot", v.object({ foo: v.string() })], + ["effect/schema", Schema.Struct({ foo: Schema.String })], +])("object input (%s)", async (_, input) => { + const fileRoute = f(["image"]) + .input(input) + .middleware((opts) => { + expectTypeOf<{ foo: string }>(opts.input); + return {}; + }) + .onUploadComplete(noop); + + type Input = inferEndpointInput; + expectTypeOf().toMatchTypeOf<{ foo: string }>(); + + const parsedInput = await getParseFn(fileRoute.inputParser)({ foo: "bar" }); + expect(parsedInput).toEqual({ foo: "bar" }); +}); + +it.each([ + ["zod", z.object({ foo: z.string() }).optional()], + ["valibot", v.optional(v.object({ foo: v.string() }))], + // FIXME: Effect's optional schema wraps the entire type which makes it incompatible with the current approach + // [ + // "effect/schema", + // Schema.Struct({ foo: Schema.String }).pipe(Schema.optional), + // ], +])("optional input (%s)", async (_, input) => { + const fileRoute = f(["image"]) + .input(input) + .middleware((opts) => { + expectTypeOf<{ foo: string } | undefined>(opts.input); + return {}; + }) + .onUploadComplete(noop); + + type Input = inferEndpointInput; + expectTypeOf().toMatchTypeOf<{ foo: string } | undefined>(); + + const parsedInput = await getParseFn(fileRoute.inputParser)({ foo: "bar" }); + expect(parsedInput).toEqual({ foo: "bar" }); +}); + +it("validation fails when input is invalid (zod)", async () => { + const fileRoute = f(["image"]).input(z.string()).onUploadComplete(noop); + const err = await getParseFn(fileRoute.inputParser)(123).catch((e) => e); + expect(err).toBeInstanceOf(z.ZodError); +}); +it("validation fails when input is invalid (valibot)", async () => { + const fileRoute = f(["image"]).input(v.string()).onUploadComplete(noop); + const err = await getParseFn(fileRoute.inputParser)(123).catch((e) => e); + expect(err).toBeInstanceOf(ParserError); + expect(Array.isArray(err.cause)).toBe(true); +}); +it("validation fails when input is invalid (effect/schema)", async () => { + const fileRoute = f(["image"]).input(Schema.String).onUploadComplete(noop); + const err = await getParseFn(fileRoute.inputParser)(123).catch((e) => e); + expect(err).toBeInstanceOf(ParserError); + expect(err.cause).toBeInstanceOf(ParseError); +}); + +it("with data transforming (zod)", async () => { + f(["image"]).input( + // @ts-expect-error - Date -> string is not JSON serializable + z.object({ + date: z.date().transform((s) => s.toISOString()), + }), + ); + + // string -> Date should work + const fileRoute = f(["image"]) + .input( + z.object({ + date: z.string().transform((s) => new Date(s)), + }), + ) + .middleware((opts) => { + expectTypeOf<{ date: Date }>(opts.input); + return {}; + }) + .onUploadComplete(noop); + + type Input = inferEndpointInput; + expectTypeOf().toMatchTypeOf<{ date: string }>(); + + const parsedInput = await getParseFn(fileRoute.inputParser)({ + date: "2024-01-01", + }); + expect(parsedInput).toEqual({ date: new Date("2024-01-01") }); +}); +it("with data transforming (valibot)", async () => { + f(["image"]).input( + // @ts-expect-error - Date -> string is not JSON serializable + v.object({ + date: v.pipe( + v.date(), + v.transform((d) => d.toISOString()), + ), + }), + ); + + // string -> Date should work + const fileRoute = f(["image"]) + .input( + v.object({ + date: v.pipe( + v.string(), + v.transform((s) => new Date(s)), + ), + }), + ) + .middleware((opts) => { + expectTypeOf<{ date: Date }>(opts.input); + return {}; + }) + .onUploadComplete(noop); + + type Input = inferEndpointInput; + expectTypeOf().toMatchTypeOf<{ date: string }>(); + + const parsedInput = await getParseFn(fileRoute.inputParser)({ + date: "2024-01-01", + }); + expect(parsedInput).toEqual({ date: new Date("2024-01-01") }); +}); +it("with data transforming (effect/schema)", async () => { + const fileRoute = f(["image"]) + .input( + Schema.Struct({ + date: Schema.Date, + }), + ) + .middleware((opts) => { + expectTypeOf<{ date: Date }>(opts.input); + return {}; + }) + .onUploadComplete(noop); + + type Input = inferEndpointInput; + expectTypeOf().toMatchTypeOf<{ date: string }>(); + + const parsedInput = await getParseFn(fileRoute.inputParser)({ + date: "2024-01-01", + }); + expect(parsedInput).toEqual({ date: new Date("2024-01-01") }); +}); + +it("type errors for non-JSON data types (zod)", () => { + f(["image"]) + // @ts-expect-error - Set is not a valid JSON type + .input(z.object({ foo: z.set(z.string()) })) + .middleware((opts) => { + return {}; + }) + .onUploadComplete(noop); +}); +it("type errors for non-JSON data types (valibot)", () => { + f(["image"]) + // @ts-expect-error - Set is not a valid JSON type + .input(v.object({ foo: v.set(v.string()) })) + .middleware((opts) => { + return {}; + }) + .onUploadComplete(noop); +}); +it("type errors for non-JSON data types (effect/schema)", () => { + f(["image"]) + // @ts-expect-error - Set is not a valid JSON type + .input(Schema.Struct({ foo: Schema.Set(Schema.String) })) + .middleware((opts) => { + return {}; + }) + .onUploadComplete(noop); +}); diff --git a/packages/uploadthing/test/upload-builder.test.ts b/packages/uploadthing/test/upload-builder.test.ts index ed9d477f06..5164e3bc86 100644 --- a/packages/uploadthing/test/upload-builder.test.ts +++ b/packages/uploadthing/test/upload-builder.test.ts @@ -1,8 +1,11 @@ /* eslint-disable @typescript-eslint/no-empty-function */ +import * as Schema from "effect/Schema"; +import * as v from "valibot"; import { expect, expectTypeOf, it } from "vitest"; import { z } from "zod"; +import { getParseFn } from "../src/internal/parser"; import { UTFiles } from "../src/internal/types"; import { createBuilder } from "../src/internal/upload-builder"; @@ -42,20 +45,6 @@ it("typeerrors for invalid input", () => { return {}; }); - f(["image"]) - // @ts-expect-error - date is not allowed - .input(z.object({ foo: z.date() })) - .middleware(() => { - return {}; - }); - - f(["image"]) - // @ts-expect-error - set is not allowed - .input(z.object({ foo: z.set() })) - .middleware(() => { - return {}; - }); - f(["image"]) .input(z.object({ foo: z.string() })) .middleware((opts) => { @@ -107,26 +96,6 @@ it("allows async middleware", () => { }); }); -it("with input", () => { - const f = createBuilder<{ req: Request; res: undefined; event: undefined }>(); - f(["image"]) - .input(z.object({ foo: z.string() })) - .middleware((opts) => { - expectTypeOf<{ foo: string }>(opts.input); - return {}; - }); -}); - -it("with optional input", () => { - const f = createBuilder<{ req: Request; res: undefined; event: undefined }>(); - f(["image"]) - .input(z.object({ foo: z.string() }).optional()) - .middleware((opts) => { - expectTypeOf<{ foo: string } | undefined>(opts.input); - return {}; - }); -}); - it("can append a customId", () => { const f = createBuilder<{ req: Request; res: undefined; event: undefined }>(); f(["image"]) @@ -167,11 +136,13 @@ it("smoke", async () => { expect(uploadable.routerConfig).toEqual(["image", "video"]); + const parsedInput = await getParseFn(uploadable.inputParser)({ foo: "bar" }); + const metadata = await uploadable.middleware({ req: new Request("http://localhost", { headers: { header1: "woohoo" }, }), - input: { foo: "bar" }, + input: parsedInput, res: undefined, event: undefined, files: [{ name: "test.txt", size: 123456, type: "text/plain" }], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 61883067a7..88c1bf682c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1622,6 +1622,9 @@ importers: '@effect/platform': specifier: 0.69.24 version: 0.69.24(effect@3.10.15) + '@standard-schema/spec': + specifier: 1.0.0-beta.3 + version: 1.0.0-beta.3 '@uploadthing/mime-types': specifier: workspace:* version: link:../mime-types @@ -1692,6 +1695,9 @@ importers: undici: specifier: ^6.6.2 version: 6.19.8 + valibot: + specifier: 1.0.0-beta.7 + version: 1.0.0-beta.7(typescript@5.6.2) vue: specifier: ^3.4.21 version: 3.4.25(typescript@5.6.2) @@ -1755,7 +1761,7 @@ importers: version: 3.10.15 next: specifier: canary - version: 15.0.4-canary.17(@playwright/test@1.45.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 15.0.4-canary.19(@playwright/test@1.45.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: 18.3.1 version: 18.3.1 @@ -1792,7 +1798,7 @@ importers: dependencies: '@uploadthing/react': specifier: npm:@uploadthing/react@6 - version: 6.8.0(next@15.0.4-canary.17(@playwright/test@1.45.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(solid-js@1.8.23)(svelte@4.2.15)(uploadthing@6.13.3(@effect/platform@0.69.24(effect@3.10.15))(express@4.21.1)(fastify@4.26.2)(h3@1.13.0)(next@15.0.4-canary.17(@playwright/test@1.45.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.14))(vue@3.4.25(typescript@5.6.3)) + version: 6.8.0(next@15.0.4-canary.19(@playwright/test@1.45.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(solid-js@1.8.23)(svelte@4.2.15)(uploadthing@6.13.3(@effect/platform@0.69.24(effect@3.10.15))(express@4.21.1)(fastify@4.26.2)(h3@1.13.0)(next@15.0.4-canary.19(@playwright/test@1.45.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.14))(vue@3.4.25(typescript@5.6.3)) clsx: specifier: 2.1.1 version: 2.1.1 @@ -1801,7 +1807,7 @@ importers: version: 3.10.15 next: specifier: canary - version: 15.0.4-canary.17(@playwright/test@1.45.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 15.0.4-canary.19(@playwright/test@1.45.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: 18.3.1 version: 18.3.1 @@ -1810,7 +1816,7 @@ importers: version: 18.3.1(react@18.3.1) uploadthing: specifier: npm:uploadthing@6 - version: 6.13.3(@effect/platform@0.69.24(effect@3.10.15))(express@4.21.1)(fastify@4.26.2)(h3@1.13.0)(next@15.0.4-canary.17(@playwright/test@1.45.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.14) + version: 6.13.3(@effect/platform@0.69.24(effect@3.10.15))(express@4.21.1)(fastify@4.26.2)(h3@1.13.0)(next@15.0.4-canary.19(@playwright/test@1.45.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.14) zod: specifier: 3.23.8 version: 3.23.8 @@ -4726,8 +4732,8 @@ packages: '@next/env@14.2.11': resolution: {integrity: sha512-HYsQRSIXwiNqvzzYThrBwq6RhXo3E0n8j8nQnAs8i4fCEo2Zf/3eS0IiRA8XnRg9Ha0YnpkyJZIZg1qEwemrHw==} - '@next/env@15.0.4-canary.17': - resolution: {integrity: sha512-WF84xjqTv+XagkwS7T5MCHgXr8TkpPGuKJjInfOcubAATfoOJ5L2liK7s9coDa09VLsnfMq4X01xT0DUv7wLyw==} + '@next/env@15.0.4-canary.19': + resolution: {integrity: sha512-OpioeuVzT9OsXVjefodLQ1B/US5lUlio5dwoJH7NLRXgYqOUBybFDDZOQ+PXzF0f55bFYjsoRFq6YnZzVkj1lA==} '@next/eslint-plugin-next@14.2.2': resolution: {integrity: sha512-q+Ec2648JtBpKiu/FSJm8HAsFXlNvioHeBCbTP12T1SGcHYwhqHULSfQgFkPgHDu3kzNp2Kem4J54bK4rPQ5SQ==} @@ -4752,8 +4758,8 @@ packages: cpu: [arm64] os: [darwin] - '@next/swc-darwin-arm64@15.0.4-canary.17': - resolution: {integrity: sha512-zyH2kwDEvuKKmL0zHGbFy7DCDc3Bd+MZC8RRaR4eAxs8j9NWWzMyg7Di8equTwHja3nk5sMLahRk7XGVkOchoA==} + '@next/swc-darwin-arm64@15.0.4-canary.19': + resolution: {integrity: sha512-n8BF+fPreS7uBUQO/6+F13FVtV/namLoS+9nqIYXL4ZyYVYWfIJKPDbjqoUK8rOnvAlBDoBSXbN9VsmMdW5sQQ==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -4764,8 +4770,8 @@ packages: cpu: [x64] os: [darwin] - '@next/swc-darwin-x64@15.0.4-canary.17': - resolution: {integrity: sha512-pHUT9kNoYIUdu4ciTIThWd2J0FLIrnSov7OZ+GUVfgxmHBSoFFvHio3eRZnD4hj4ifJ5dU4BR0QZYe1CPafYaw==} + '@next/swc-darwin-x64@15.0.4-canary.19': + resolution: {integrity: sha512-iDyFGSUuxPeCCOwuo5nj8usUiZXwmtQOM0RpTSO1GAGIeDH49BWWqEYIiRFcS8dhEyDGZNl3SlRS4xmHaeT8cg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -4776,8 +4782,8 @@ packages: cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-gnu@15.0.4-canary.17': - resolution: {integrity: sha512-X5KlH327Dn3crLCd+2VuH57FoIgg3XOr/7+MxBH3wpDyihT2QopU/C5tAOe2kcY3d0DBpytuTKtOoV3oQ2T57w==} + '@next/swc-linux-arm64-gnu@15.0.4-canary.19': + resolution: {integrity: sha512-qV2xF7V1yLuNnDZQ6bkzDuZbUGV7ofYv0Q9IgogPTx2pwChpwSXVK2OQAoU9BqVcIMBThVdVa5ILMWvoG5TfEw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -4788,8 +4794,8 @@ packages: cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@15.0.4-canary.17': - resolution: {integrity: sha512-yWQGdGTH7SYE9RT0z+Dls8UxqxpdjiNJglWz1kL819Zovh1LNDUPWSk1Vb1w6z+bxX+Fo54ttxqeh2FIbjl9Iw==} + '@next/swc-linux-arm64-musl@15.0.4-canary.19': + resolution: {integrity: sha512-nykBD1+5RrtqC5fT87wt3CPx04q8tUKKCixHzdmXIcycEYP+RTEucq04gvEITkHuC6DYg9IOGKeqlQqSugRYWA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -4800,8 +4806,8 @@ packages: cpu: [x64] os: [linux] - '@next/swc-linux-x64-gnu@15.0.4-canary.17': - resolution: {integrity: sha512-195PFWM+Ah+TigBZG2qBZyo7pciB4BY/QarizMcr9rUDAXejZT7AYM2f4rkAQMbz0jftgZEuFjqOfCP0cbGcMg==} + '@next/swc-linux-x64-gnu@15.0.4-canary.19': + resolution: {integrity: sha512-aJFVPeFSdQwQmhBll8XW+e8yrYF0M6AKLUsXOUkAd6XvP1m/8Waps+DNkdMmjI1eD89b+C9SufjP8S1HO+LWgQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -4812,8 +4818,8 @@ packages: cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@15.0.4-canary.17': - resolution: {integrity: sha512-f+eQPl5h+kMZkCwi90B4bmx3zCDjl9Adw66hE92djSibWMMlNGsxRVOXAvZglJWaZr8RffpCHUGyjyL003Uiqg==} + '@next/swc-linux-x64-musl@15.0.4-canary.19': + resolution: {integrity: sha512-owcSYpr0sLz94LJPgQ1z/FgoFsY40uHMG1nZBD0ar2Cj5/zbeHGzATpLoeg6UWG2M441KBRpeTPvNGIfTEQ4xQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -4824,8 +4830,8 @@ packages: cpu: [arm64] os: [win32] - '@next/swc-win32-arm64-msvc@15.0.4-canary.17': - resolution: {integrity: sha512-mNLMrLBPG8TKkymQqJH6ZLAj61oGUK8GnXMsqmZR/+OrsIYlzjHjJbYpLPStyxezChhmmBIwBnf9Olm4++8irQ==} + '@next/swc-win32-arm64-msvc@15.0.4-canary.19': + resolution: {integrity: sha512-dyeltaQ40350j+FExwrlV4YXZnYXQE/SQ17JEQHucOX9lrlUJ/XtwIEJSLSeu+fVjWq5gu6uV6wQejquwECngg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -4842,8 +4848,8 @@ packages: cpu: [x64] os: [win32] - '@next/swc-win32-x64-msvc@15.0.4-canary.17': - resolution: {integrity: sha512-E1OVH5mnfQprRcRXTVvPWro/ywyqj+ojRNzhkZzLyoy7cODQdyD6uIq+jeMltQg8BQIxfG8EeHjgTimwoixDPA==} + '@next/swc-win32-x64-msvc@15.0.4-canary.19': + resolution: {integrity: sha512-dbhxrOJXrE5Z1QTb9iArnc9MZRTinP76vKrxu1yvKHJJ1tXIe8wwpXAmPP4tK4wZ9mnlJIMc8fpuoNla38cFNA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -6180,6 +6186,9 @@ packages: '@solidjs/start@1.0.6': resolution: {integrity: sha512-O5knaeqDBx+nKLJRm5ZJurnXZtIYBOwOreQ10APaVtVjKIKKRC5HxJ1Kwqg7atOQNNDgsF0pzhW218KseaZ1UA==} + '@standard-schema/spec@1.0.0-beta.3': + resolution: {integrity: sha512-0ifF3BjA1E8SY9C+nUew8RefNOIq0cDlYALPty4rhUm8Rrl6tCM8hBT4bhGhx7I7iXD0uAgt50lgo8dD73ACMw==} + '@storybook/codemod@8.2.1': resolution: {integrity: sha512-LYvVLOKj5mDbbAPLrxd3BWQaemTqp2y5RV5glNqsPq3FoFX4rn4VnWb5X/YBWsMqqCK+skimH/f7HQ5fDvWubg==} @@ -12601,8 +12610,8 @@ packages: sass: optional: true - next@15.0.4-canary.17: - resolution: {integrity: sha512-2lNBfL42lCqpc2R6//sndFwCB8nUC0VlpLDQUPiOCh5Y/oFQlB36so7IaXSboyle7enwsZz631TvewYnYsN1yQ==} + next@15.0.4-canary.19: + resolution: {integrity: sha512-TFYV2o4v8gqti5JNKaUujI1rsPQyNkZ2NZCpcOhme+LlP99yD+IUOibCRvveySNNZuH2pvUzpzJsXMjpZOc9PA==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: @@ -16097,6 +16106,14 @@ packages: engines: {node: '>=8'} hasBin: true + valibot@1.0.0-beta.7: + resolution: {integrity: sha512-8CsDu3tqyg7quEHMzCOYdQ/d9NlmVQKtd4AlFje6oJpvqo70EIZjSakKIeWltJyNAiUtdtLe0LAk4625gavoeQ==} + peerDependencies: + typescript: '>=5' + peerDependenciesMeta: + typescript: + optional: true + valid-url@1.0.9: resolution: {integrity: sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA==} @@ -20035,7 +20052,7 @@ snapshots: '@next/env@14.2.11': {} - '@next/env@15.0.4-canary.17': {} + '@next/env@15.0.4-canary.19': {} '@next/eslint-plugin-next@14.2.2': dependencies: @@ -20055,43 +20072,43 @@ snapshots: '@next/swc-darwin-arm64@14.2.11': optional: true - '@next/swc-darwin-arm64@15.0.4-canary.17': + '@next/swc-darwin-arm64@15.0.4-canary.19': optional: true '@next/swc-darwin-x64@14.2.11': optional: true - '@next/swc-darwin-x64@15.0.4-canary.17': + '@next/swc-darwin-x64@15.0.4-canary.19': optional: true '@next/swc-linux-arm64-gnu@14.2.11': optional: true - '@next/swc-linux-arm64-gnu@15.0.4-canary.17': + '@next/swc-linux-arm64-gnu@15.0.4-canary.19': optional: true '@next/swc-linux-arm64-musl@14.2.11': optional: true - '@next/swc-linux-arm64-musl@15.0.4-canary.17': + '@next/swc-linux-arm64-musl@15.0.4-canary.19': optional: true '@next/swc-linux-x64-gnu@14.2.11': optional: true - '@next/swc-linux-x64-gnu@15.0.4-canary.17': + '@next/swc-linux-x64-gnu@15.0.4-canary.19': optional: true '@next/swc-linux-x64-musl@14.2.11': optional: true - '@next/swc-linux-x64-musl@15.0.4-canary.17': + '@next/swc-linux-x64-musl@15.0.4-canary.19': optional: true '@next/swc-win32-arm64-msvc@14.2.11': optional: true - '@next/swc-win32-arm64-msvc@15.0.4-canary.17': + '@next/swc-win32-arm64-msvc@15.0.4-canary.19': optional: true '@next/swc-win32-ia32-msvc@14.2.11': @@ -20100,7 +20117,7 @@ snapshots: '@next/swc-win32-x64-msvc@14.2.11': optional: true - '@next/swc-win32-x64-msvc@15.0.4-canary.17': + '@next/swc-win32-x64-msvc@15.0.4-canary.19': optional: true '@nodelib/fs.scandir@2.1.5': @@ -22671,6 +22688,8 @@ snapshots: - vinxi - vite + '@standard-schema/spec@1.0.0-beta.3': {} + '@storybook/codemod@8.2.1': dependencies: '@babel/core': 7.25.8 @@ -24052,16 +24071,16 @@ snapshots: '@uploadthing/mime-types@0.2.10': {} - '@uploadthing/react@6.8.0(next@15.0.4-canary.17(@playwright/test@1.45.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(solid-js@1.8.23)(svelte@4.2.15)(uploadthing@6.13.3(@effect/platform@0.69.24(effect@3.10.15))(express@4.21.1)(fastify@4.26.2)(h3@1.13.0)(next@15.0.4-canary.17(@playwright/test@1.45.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.14))(vue@3.4.25(typescript@5.6.3))': + '@uploadthing/react@6.8.0(next@15.0.4-canary.19(@playwright/test@1.45.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(solid-js@1.8.23)(svelte@4.2.15)(uploadthing@6.13.3(@effect/platform@0.69.24(effect@3.10.15))(express@4.21.1)(fastify@4.26.2)(h3@1.13.0)(next@15.0.4-canary.19(@playwright/test@1.45.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.14))(vue@3.4.25(typescript@5.6.3))': dependencies: '@uploadthing/dropzone': 0.4.1(react@18.3.1)(solid-js@1.8.23)(svelte@4.2.15)(vue@3.4.25(typescript@5.6.3)) '@uploadthing/shared': 6.7.9 file-selector: 0.6.0 react: 18.3.1 tailwind-merge: 2.3.0 - uploadthing: 6.13.3(@effect/platform@0.69.24(effect@3.10.15))(express@4.21.1)(fastify@4.26.2)(h3@1.13.0)(next@15.0.4-canary.17(@playwright/test@1.45.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.14) + uploadthing: 6.13.3(@effect/platform@0.69.24(effect@3.10.15))(express@4.21.1)(fastify@4.26.2)(h3@1.13.0)(next@15.0.4-canary.19(@playwright/test@1.45.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.14) optionalDependencies: - next: 15.0.4-canary.17(@playwright/test@1.45.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 15.0.4-canary.19(@playwright/test@1.45.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) transitivePeerDependencies: - solid-js - svelte @@ -31260,9 +31279,9 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@15.0.4-canary.17(@playwright/test@1.45.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@15.0.4-canary.19(@playwright/test@1.45.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@next/env': 15.0.4-canary.17 + '@next/env': 15.0.4-canary.19 '@swc/counter': 0.1.3 '@swc/helpers': 0.5.13 busboy: 1.6.0 @@ -31272,14 +31291,14 @@ snapshots: react-dom: 18.3.1(react@18.3.1) styled-jsx: 5.1.6(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 15.0.4-canary.17 - '@next/swc-darwin-x64': 15.0.4-canary.17 - '@next/swc-linux-arm64-gnu': 15.0.4-canary.17 - '@next/swc-linux-arm64-musl': 15.0.4-canary.17 - '@next/swc-linux-x64-gnu': 15.0.4-canary.17 - '@next/swc-linux-x64-musl': 15.0.4-canary.17 - '@next/swc-win32-arm64-msvc': 15.0.4-canary.17 - '@next/swc-win32-x64-msvc': 15.0.4-canary.17 + '@next/swc-darwin-arm64': 15.0.4-canary.19 + '@next/swc-darwin-x64': 15.0.4-canary.19 + '@next/swc-linux-arm64-gnu': 15.0.4-canary.19 + '@next/swc-linux-arm64-musl': 15.0.4-canary.19 + '@next/swc-linux-x64-gnu': 15.0.4-canary.19 + '@next/swc-linux-x64-musl': 15.0.4-canary.19 + '@next/swc-win32-arm64-msvc': 15.0.4-canary.19 + '@next/swc-win32-x64-msvc': 15.0.4-canary.19 '@playwright/test': 1.45.0 sharp: 0.33.5 transitivePeerDependencies: @@ -35903,7 +35922,7 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.0 - uploadthing@6.13.3(@effect/platform@0.69.24(effect@3.10.15))(express@4.21.1)(fastify@4.26.2)(h3@1.13.0)(next@15.0.4-canary.17(@playwright/test@1.45.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.14): + uploadthing@6.13.3(@effect/platform@0.69.24(effect@3.10.15))(express@4.21.1)(fastify@4.26.2)(h3@1.13.0)(next@15.0.4-canary.19(@playwright/test@1.45.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.14): dependencies: '@effect/schema': 0.68.18(effect@3.4.8) '@uploadthing/mime-types': 0.2.10 @@ -35916,7 +35935,7 @@ snapshots: express: 4.21.1 fastify: 4.26.2 h3: 1.13.0 - next: 15.0.4-canary.17(@playwright/test@1.45.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 15.0.4-canary.19(@playwright/test@1.45.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) tailwindcss: 3.4.14 uqr@0.1.2: {} @@ -35988,6 +36007,10 @@ snapshots: kleur: 4.1.5 sade: 1.8.1 + valibot@1.0.0-beta.7(typescript@5.6.2): + optionalDependencies: + typescript: 5.6.2 + valid-url@1.0.9: {} validate-html-nesting@1.2.2: {}