diff --git a/.changeset/funny-beans-peel.md b/.changeset/funny-beans-peel.md new file mode 100644 index 00000000..aa153891 --- /dev/null +++ b/.changeset/funny-beans-peel.md @@ -0,0 +1,7 @@ +--- +"@effect/platform-node": minor +"@effect/platform-bun": minor +"@effect/platform": minor +--- + +rename server FormData module to Multipart diff --git a/docs/platform-bun/HttpServer.ts.md b/docs/platform-bun/HttpServer.ts.md index 02df2eb6..7a9accdb 100644 --- a/docs/platform-bun/HttpServer.ts.md +++ b/docs/platform-bun/HttpServer.ts.md @@ -17,9 +17,9 @@ Added in v1.0.0 - [body](#body) - [error](#error) - [etag](#etag) - - [formData](#formdata) - [headers](#headers) - [middleware](#middleware) + - [multipart](#multipart) - [request](#request) - [response](#response) - [router](#router) @@ -82,44 +82,44 @@ Added in v1.0.0 - Docs: [Http/Etag](https://effect-ts.github.io/platform/platform-node/Http/Etag.ts.html) - Module: `@effect/platform-node/Http/Etag` -## formData +## headers **Signature** ```ts -export declare const formData: typeof formData +export declare const headers: typeof headers ``` Added in v1.0.0 -- Docs: [Http/FormData](https://effect-ts.github.io/platform/platform-node/Http/FormData.ts.html) -- Module: `@effect/platform-node/Http/FormData` +- Docs: [Http/Headers](https://effect-ts.github.io/platform/platform/Http/Headers.ts.html) +- Module: `@effect/platform/Http/Headers` -## headers +## middleware **Signature** ```ts -export declare const headers: typeof headers +export declare const middleware: typeof middleware ``` Added in v1.0.0 -- Docs: [Http/Headers](https://effect-ts.github.io/platform/platform/Http/Headers.ts.html) -- Module: `@effect/platform/Http/Headers` +- Docs: [Http/Middleware](https://effect-ts.github.io/platform/platform/Http/Middleware.ts.html) +- Module: `@effect/platform/Http/Middleware` -## middleware +## multipart **Signature** ```ts -export declare const middleware: typeof middleware +export declare const multipart: typeof multipart ``` Added in v1.0.0 -- Docs: [Http/Middleware](https://effect-ts.github.io/platform/platform/Http/Middleware.ts.html) -- Module: `@effect/platform/Http/Middleware` +- Docs: [Http/Multipart](https://effect-ts.github.io/platform/platform-node/Http/Multipart.ts.html) +- Module: `@effect/platform-node/Http/Multipart` ## request diff --git a/docs/platform-node/Http/FormData.ts.md b/docs/platform-node/Http/Multipart.ts.md similarity index 50% rename from docs/platform-node/Http/FormData.ts.md rename to docs/platform-node/Http/Multipart.ts.md index 61f456ea..48b97182 100644 --- a/docs/platform-node/Http/FormData.ts.md +++ b/docs/platform-node/Http/Multipart.ts.md @@ -1,40 +1,40 @@ --- -title: Http/FormData.ts +title: Http/Multipart.ts nav_order: 7 parent: "@effect/platform-node" --- -## FormData overview +## Multipart overview Added in v1.0.0 -Also includes exports from [`@effect/platform/Http/FormData`](https://effect-ts.github.io/platform/platform/Http/FormData.ts.html). +Also includes exports from [`@effect/platform/Http/Multipart`](https://effect-ts.github.io/platform/platform/Http/Multipart.ts.html). ---

Table of contents

- [constructors](#constructors) - - [formData](#formdata) + - [persisted](#persisted) - [stream](#stream) - [conversions](#conversions) - [fileToReadable](#filetoreadable) - [exports](#exports) - - [From "@effect/platform/Http/FormData"](#from-effectplatformhttpformdata) + - [From "@effect/platform/Http/Multipart"](#from-effectplatformhttpmultipart) --- # constructors -## formData +## persisted **Signature** ```ts -export declare const formData: ( +export declare const persisted: ( source: Readable, headers: IncomingHttpHeaders -) => Effect.Effect +) => Effect.Effect ``` Added in v1.0.0 @@ -47,7 +47,7 @@ Added in v1.0.0 export declare const stream: ( source: Readable, headers: IncomingHttpHeaders -) => Stream.Stream +) => Stream.Stream ``` Added in v1.0.0 @@ -59,21 +59,21 @@ Added in v1.0.0 **Signature** ```ts -export declare const fileToReadable: (file: FormData.File) => Readable +export declare const fileToReadable: (file: Multipart.File) => Readable ``` Added in v1.0.0 # exports -## From "@effect/platform/Http/FormData" +## From "@effect/platform/Http/Multipart" -Re-exports all named exports from the "@effect/platform/Http/FormData" module. +Re-exports all named exports from the "@effect/platform/Http/Multipart" module. **Signature** ```ts -export * from "@effect/platform/Http/FormData" +export * from "@effect/platform/Http/Multipart" ``` Added in v1.0.0 diff --git a/docs/platform-node/HttpServer.ts.md b/docs/platform-node/HttpServer.ts.md index 9fdca599..9c96a0cc 100644 --- a/docs/platform-node/HttpServer.ts.md +++ b/docs/platform-node/HttpServer.ts.md @@ -17,9 +17,9 @@ Added in v1.0.0 - [body](#body) - [error](#error) - [etag](#etag) - - [formData](#formdata) - [headers](#headers) - [middleware](#middleware) + - [multipart](#multipart) - [request](#request) - [response](#response) - [router](#router) @@ -82,44 +82,44 @@ Added in v1.0.0 - Docs: [Http/Etag](https://effect-ts.github.io/platform/platform-node/Http/Etag.ts.html) - Module: `@effect/platform-node/Http/Etag` -## formData +## headers **Signature** ```ts -export declare const formData: typeof formData +export declare const headers: typeof headers ``` Added in v1.0.0 -- Docs: [Http/FormData](https://effect-ts.github.io/platform/platform-node/Http/FormData.ts.html) -- Module: `@effect/platform-node/Http/FormData` +- Docs: [Http/Headers](https://effect-ts.github.io/platform/platform/Http/Headers.ts.html) +- Module: `@effect/platform/Http/Headers` -## headers +## middleware **Signature** ```ts -export declare const headers: typeof headers +export declare const middleware: typeof middleware ``` Added in v1.0.0 -- Docs: [Http/Headers](https://effect-ts.github.io/platform/platform/Http/Headers.ts.html) -- Module: `@effect/platform/Http/Headers` +- Docs: [Http/Middleware](https://effect-ts.github.io/platform/platform/Http/Middleware.ts.html) +- Module: `@effect/platform/Http/Middleware` -## middleware +## multipart **Signature** ```ts -export declare const middleware: typeof middleware +export declare const multipart: typeof multipart ``` Added in v1.0.0 -- Docs: [Http/Middleware](https://effect-ts.github.io/platform/platform/Http/Middleware.ts.html) -- Module: `@effect/platform/Http/Middleware` +- Docs: [Http/Multipart](https://effect-ts.github.io/platform/platform-node/Http/Multipart.ts.html) +- Module: `@effect/platform-node/Http/Multipart` ## request diff --git a/docs/platform/Http/Headers.ts.md b/docs/platform/Http/Headers.ts.md index b38814e3..181b0be0 100644 --- a/docs/platform/Http/Headers.ts.md +++ b/docs/platform/Http/Headers.ts.md @@ -1,6 +1,6 @@ --- title: Http/Headers.ts -nav_order: 14 +nav_order: 13 parent: "@effect/platform" --- diff --git a/docs/platform/Http/IncomingMessage.ts.md b/docs/platform/Http/IncomingMessage.ts.md index 220a7647..9c03d4b0 100644 --- a/docs/platform/Http/IncomingMessage.ts.md +++ b/docs/platform/Http/IncomingMessage.ts.md @@ -1,6 +1,6 @@ --- title: Http/IncomingMessage.ts -nav_order: 15 +nav_order: 14 parent: "@effect/platform" --- diff --git a/docs/platform/Http/Method.ts.md b/docs/platform/Http/Method.ts.md index d754b858..e1fa3445 100644 --- a/docs/platform/Http/Method.ts.md +++ b/docs/platform/Http/Method.ts.md @@ -1,6 +1,6 @@ --- title: Http/Method.ts -nav_order: 16 +nav_order: 15 parent: "@effect/platform" --- diff --git a/docs/platform/Http/Middleware.ts.md b/docs/platform/Http/Middleware.ts.md index ca3b9805..f02fa0ad 100644 --- a/docs/platform/Http/Middleware.ts.md +++ b/docs/platform/Http/Middleware.ts.md @@ -1,6 +1,6 @@ --- title: Http/Middleware.ts -nav_order: 17 +nav_order: 16 parent: "@effect/platform" --- diff --git a/docs/platform/Http/FormData.ts.md b/docs/platform/Http/Multipart.ts.md similarity index 82% rename from docs/platform/Http/FormData.ts.md rename to docs/platform/Http/Multipart.ts.md index d2db0057..da0e72dd 100644 --- a/docs/platform/Http/FormData.ts.md +++ b/docs/platform/Http/Multipart.ts.md @@ -1,10 +1,10 @@ --- -title: Http/FormData.ts -nav_order: 13 +title: Http/Multipart.ts +nav_order: 17 parent: "@effect/platform" --- -## FormData overview +## Multipart overview Added in v1.0.0 @@ -13,12 +13,12 @@ Added in v1.0.0

Table of contents

- [constructors](#constructors) - - [formData](#formdata) - [makeChannel](#makechannel) - [makeConfig](#makeconfig) + - [toPersisted](#topersisted) - [errors](#errors) - - [FormDataError](#formdataerror) - - [FormDataError (interface)](#formdataerror-interface) + - [MultipartError](#multiparterror) + - [MultipartError (interface)](#multiparterror-interface) - [fiber refs](#fiber-refs) - [fieldMimeTypes](#fieldmimetypes) - [maxFieldSize](#maxfieldsize) @@ -32,8 +32,8 @@ Added in v1.0.0 - [Field (interface)](#field-interface) - [File (interface)](#file-interface) - [Part (type alias)](#part-type-alias) + - [Persisted (interface)](#persisted-interface) - [PersistedFile (interface)](#persistedfile-interface) - - [PersistedFormData (interface)](#persistedformdata-interface) - [refinements](#refinements) - [isField](#isfield) - [schema](#schema) @@ -53,62 +53,62 @@ Added in v1.0.0 # constructors -## formData +## makeChannel **Signature** ```ts -export declare const formData: ( - stream: Stream.Stream, - writeFile?: ((path: string, file: File) => Effect.Effect) | undefined -) => Effect.Effect +export declare const makeChannel: ( + headers: Record, + bufferSize?: number +) => Channel.Channel, unknown, MultipartError | IE, Chunk.Chunk, unknown> ``` Added in v1.0.0 -## makeChannel +## makeConfig **Signature** ```ts -export declare const makeChannel: ( - headers: Record, - bufferSize?: number -) => Channel.Channel, unknown, FormDataError | IE, Chunk.Chunk, unknown> +export declare const makeConfig: (headers: Record) => Effect.Effect ``` Added in v1.0.0 -## makeConfig +## toPersisted **Signature** ```ts -export declare const makeConfig: (headers: Record) => Effect.Effect +export declare const toPersisted: ( + stream: Stream.Stream, + writeFile?: ((path: string, file: File) => Effect.Effect) | undefined +) => Effect.Effect ``` Added in v1.0.0 # errors -## FormDataError +## MultipartError **Signature** ```ts -export declare const FormDataError: (reason: FormDataError["reason"], error: unknown) => FormDataError +export declare const MultipartError: (reason: MultipartError["reason"], error: unknown) => MultipartError ``` Added in v1.0.0 -## FormDataError (interface) +## MultipartError (interface) **Signature** ```ts -export interface FormDataError extends Data.Case { +export interface MultipartError extends Data.Case { readonly [ErrorTypeId]: ErrorTypeId - readonly _tag: "FormDataError" + readonly _tag: "MultipartError" readonly reason: "FileTooLarge" | "FieldTooLarge" | "BodyTooLarge" | "TooManyParts" | "InternalError" | "Parse" readonly error: unknown } @@ -237,7 +237,7 @@ export interface File extends Part.Proto { readonly key: string readonly name: string readonly contentType: string - readonly content: Stream.Stream + readonly content: Stream.Stream } ``` @@ -253,29 +253,29 @@ export type Part = Field | File Added in v1.0.0 -## PersistedFile (interface) +## Persisted (interface) **Signature** ```ts -export interface PersistedFile extends Part.Proto { - readonly _tag: "PersistedFile" - readonly key: string - readonly name: string - readonly contentType: string - readonly path: string +export interface Persisted { + readonly [key: string]: ReadonlyArray | string } ``` Added in v1.0.0 -## PersistedFormData (interface) +## PersistedFile (interface) **Signature** ```ts -export interface PersistedFormData { - readonly [key: string]: ReadonlyArray | string +export interface PersistedFile extends Part.Proto { + readonly _tag: "PersistedFile" + readonly key: string + readonly name: string + readonly contentType: string + readonly path: string } ``` @@ -313,8 +313,8 @@ Added in v1.0.0 export declare const schemaJson: ( schema: Schema.Schema ) => { - (field: string): (formData: PersistedFormData) => Effect.Effect - (formData: PersistedFormData, field: string): Effect.Effect + (field: string): (persisted: Persisted) => Effect.Effect + (persisted: Persisted, field: string): Effect.Effect } ``` @@ -325,9 +325,9 @@ Added in v1.0.0 **Signature** ```ts -export declare const schemaPersisted: ( +export declare const schemaPersisted: ( schema: Schema.Schema -) => (formData: PersistedFormData) => Effect.Effect +) => (persisted: Persisted) => Effect.Effect ``` Added in v1.0.0 diff --git a/docs/platform/Http/ServerRequest.ts.md b/docs/platform/Http/ServerRequest.ts.md index ee2e8b28..78d8f17f 100644 --- a/docs/platform/Http/ServerRequest.ts.md +++ b/docs/platform/Http/ServerRequest.ts.md @@ -13,7 +13,7 @@ Added in v1.0.0

Table of contents

- [accessors](#accessors) - - [persistedFormData](#persistedformdata) + - [persistedMultipart](#persistedmultipart) - [context](#context) - [ServerRequest](#serverrequest) - [conversions](#conversions) @@ -23,10 +23,11 @@ Added in v1.0.0 - [models](#models) - [ServerRequest (interface)](#serverrequest-interface) - [schema](#schema) + - [schemaBodyForm](#schemabodyform) - [schemaBodyJson](#schemabodyjson) + - [schemaBodyMultipart](#schemabodymultipart) + - [schemaBodyMultipartJson](#schemabodymultipartjson) - [schemaBodyUrlParams](#schemabodyurlparams) - - [schemaFormData](#schemaformdata) - - [schemaFormDataJson](#schemaformdatajson) - [schemaHeaders](#schemaheaders) - [type ids](#type-ids) - [TypeId](#typeid) @@ -36,14 +37,14 @@ Added in v1.0.0 # accessors -## persistedFormData +## persistedMultipart **Signature** ```ts -export declare const persistedFormData: Effect.Effect< +export declare const persistedMultipart: Effect.Effect< Scope.Scope | Path.Path | FileSystem.FileSystem | ServerRequest, - FormData.FormDataError, + Multipart.MultipartError, unknown > ``` @@ -100,12 +101,12 @@ export interface ServerRequest extends IncomingMessage.IncomingMessage - readonly formDataStream: Stream.Stream + readonly multipartStream: Stream.Stream readonly modify: (options: { readonly url?: string @@ -119,64 +120,80 @@ Added in v1.0.0 # schema -## schemaBodyJson +## schemaBodyForm **Signature** ```ts -export declare const schemaBodyJson: ( +export declare const schemaBodyForm: ( schema: Schema.Schema -) => Effect.Effect +) => Effect.Effect< + Scope.Scope | Path.Path | FileSystem.FileSystem | ServerRequest, + Multipart.MultipartError | ParseResult.ParseError | Error.RequestError, + A +> ``` Added in v1.0.0 -## schemaBodyUrlParams +## schemaBodyJson **Signature** ```ts -export declare const schemaBodyUrlParams: >, A>( +export declare const schemaBodyJson: ( schema: Schema.Schema ) => Effect.Effect ``` Added in v1.0.0 -## schemaFormData +## schemaBodyMultipart **Signature** ```ts -export declare const schemaFormData: ( +export declare const schemaBodyMultipart: ( schema: Schema.Schema ) => Effect.Effect< Scope.Scope | Path.Path | FileSystem.FileSystem | ServerRequest, - FormData.FormDataError | ParseResult.ParseError, + Multipart.MultipartError | ParseResult.ParseError, A > ``` Added in v1.0.0 -## schemaFormDataJson +## schemaBodyMultipartJson **Signature** ```ts -export declare const schemaFormDataJson: ( +export declare const schemaBodyMultipartJson: ( schema: Schema.Schema ) => ( field: string ) => Effect.Effect< Scope.Scope | Path.Path | FileSystem.FileSystem | ServerRequest, - FormData.FormDataError | ParseResult.ParseError | Error.RequestError, + Multipart.MultipartError | ParseResult.ParseError | Error.RequestError, A > ``` Added in v1.0.0 +## schemaBodyUrlParams + +**Signature** + +```ts +export declare const schemaBodyUrlParams: >, A>( + schema: Schema.Schema +) => Effect.Effect +``` + +Added in v1.0.0 + ## schemaHeaders **Signature** diff --git a/docs/platform/HttpServer.ts.md b/docs/platform/HttpServer.ts.md index 1f925319..3759aedf 100644 --- a/docs/platform/HttpServer.ts.md +++ b/docs/platform/HttpServer.ts.md @@ -16,9 +16,9 @@ Added in v1.0.0 - [app](#app) - [body](#body) - [error](#error) - - [formData](#formdata) - [headers](#headers) - [middleware](#middleware) + - [multipart](#multipart) - [request](#request) - [response](#response) - [router](#router) @@ -67,44 +67,44 @@ Added in v1.0.0 - Docs: [Http/ServerError](https://effect-ts.github.io/platform/platform/Http/ServerError.html) - Module: `@effect/platform/Http/ServerError` -## formData +## headers **Signature** ```ts -export declare const formData: typeof formData +export declare const headers: typeof headers ``` Added in v1.0.0 -- Docs: [Http/FormData](https://effect-ts.github.io/platform/platform/Http/FormData.html) -- Module: `@effect/platform/Http/FormData` +- Docs: [Http/Headers](https://effect-ts.github.io/platform/platform/Http/Headers.html) +- Module: `@effect/platform/Http/Headers` -## headers +## middleware **Signature** ```ts -export declare const headers: typeof headers +export declare const middleware: typeof middleware ``` Added in v1.0.0 -- Docs: [Http/Headers](https://effect-ts.github.io/platform/platform/Http/Headers.html) -- Module: `@effect/platform/Http/Headers` +- Docs: [Http/Middleware](https://effect-ts.github.io/platform/platform/Http/Middleware.html) +- Module: `@effect/platform/Http/Middleware` -## middleware +## multipart **Signature** ```ts -export declare const middleware: typeof middleware +export declare const multipart: typeof multipart ``` Added in v1.0.0 -- Docs: [Http/Middleware](https://effect-ts.github.io/platform/platform/Http/Middleware.html) -- Module: `@effect/platform/Http/Middleware` +- Docs: [Http/Multipart](https://effect-ts.github.io/platform/platform/Http/Multipart.html) +- Module: `@effect/platform/Http/Multipart` ## request diff --git a/packages/platform-bun/examples/http-router.ts b/packages/platform-bun/examples/http-router.ts index c46f8a95..f3556b9f 100644 --- a/packages/platform-bun/examples/http-router.ts +++ b/packages/platform-bun/examples/http-router.ts @@ -20,8 +20,8 @@ const HttpLive = Http.router.empty.pipe( Http.router.post( "/upload", Effect.gen(function*(_) { - const data = yield* _(Http.request.schemaFormData(Schema.struct({ - files: Http.formData.filesSchema + const data = yield* _(Http.request.schemaBodyForm(Schema.struct({ + files: Http.multipart.filesSchema }))) console.log("got files", data.files) return Http.response.empty() diff --git a/packages/platform-bun/src/HttpServer.ts b/packages/platform-bun/src/HttpServer.ts index 82444d53..e5420e2b 100644 --- a/packages/platform-bun/src/HttpServer.ts +++ b/packages/platform-bun/src/HttpServer.ts @@ -2,7 +2,7 @@ * @since 1.0.0 */ import * as etag from "@effect/platform-node/Http/Etag" -import * as formData from "@effect/platform-node/Http/FormData" +import * as multipart from "@effect/platform-node/Http/Multipart" import * as app from "@effect/platform/Http/App" import * as body from "@effect/platform/Http/Body" import * as headers from "@effect/platform/Http/Headers" @@ -43,13 +43,6 @@ export { * - Module: `@effect/platform-node/Http/Etag` */ etag, - /** - * @since 1.0.0 - * - * - Docs: [Http/FormData](https://effect-ts.github.io/platform/platform-node/Http/FormData.ts.html) - * - Module: `@effect/platform-node/Http/FormData` - */ - formData, /** * @since 1.0.0 * @@ -64,6 +57,13 @@ export { * - Module: `@effect/platform/Http/Middleware` */ middleware, + /** + * @since 1.0.0 + * + * - Docs: [Http/Multipart](https://effect-ts.github.io/platform/platform-node/Http/Multipart.ts.html) + * - Module: `@effect/platform-node/Http/Multipart` + */ + multipart, /** * @since 1.0.0 * diff --git a/packages/platform-bun/src/internal/http/server.ts b/packages/platform-bun/src/internal/http/server.ts index 88e61ac1..f7143b34 100644 --- a/packages/platform-bun/src/internal/http/server.ts +++ b/packages/platform-bun/src/internal/http/server.ts @@ -1,5 +1,5 @@ /// -import * as FormData from "@effect/platform-node/Http/FormData" +import * as Multipart from "@effect/platform-node/Http/Multipart" import type * as FileSystem from "@effect/platform/FileSystem" import * as App from "@effect/platform/Http/App" import * as Headers from "@effect/platform/Http/Headers" @@ -278,29 +278,29 @@ class ServerRequestImpl implements ServerRequest.ServerRequest { })) } - private formDataEffect: + private multipartEffect: | Effect.Effect< Scope.Scope | FileSystem.FileSystem | Path.Path, - FormData.FormDataError, - FormData.PersistedFormData + Multipart.MultipartError, + Multipart.Persisted > | undefined - get formData(): Effect.Effect< + get multipart(): Effect.Effect< Scope.Scope | FileSystem.FileSystem | Path.Path, - FormData.FormDataError, - FormData.PersistedFormData + Multipart.MultipartError, + Multipart.Persisted > { - if (this.formDataEffect) { - return this.formDataEffect + if (this.multipartEffect) { + return this.multipartEffect } - this.formDataEffect = Effect.runSync(Effect.cached( - FormData.formData(Readable.fromWeb(this.source.body! as any), this.headers) + this.multipartEffect = Effect.runSync(Effect.cached( + Multipart.persisted(Readable.fromWeb(this.source.body! as any), this.headers) )) - return this.formDataEffect + return this.multipartEffect } - get formDataStream(): Stream.Stream { - return FormData.stream(Readable.fromWeb(this.source.body! as any), this.headers) + get multipartStream(): Stream.Stream { + return Multipart.stream(Readable.fromWeb(this.source.body! as any), this.headers) } private arrayBufferEffect: Effect.Effect | undefined diff --git a/packages/platform-node/examples/http-router.ts b/packages/platform-node/examples/http-router.ts index df01bbdc..b4bd8053 100644 --- a/packages/platform-node/examples/http-router.ts +++ b/packages/platform-node/examples/http-router.ts @@ -25,8 +25,8 @@ const HttpLive = Http.router.empty.pipe( Http.router.post( "/upload", Effect.gen(function*(_) { - const data = yield* _(Http.request.schemaFormData(Schema.struct({ - files: Http.formData.filesSchema + const data = yield* _(Http.request.schemaBodyForm(Schema.struct({ + files: Http.multipart.filesSchema }))) console.log("got files", data.files) return Http.response.empty() diff --git a/packages/platform-node/src/Http/FormData.ts b/packages/platform-node/src/Http/Multipart.ts similarity index 56% rename from packages/platform-node/src/Http/FormData.ts rename to packages/platform-node/src/Http/Multipart.ts index b021db7b..57d8b7e6 100644 --- a/packages/platform-node/src/Http/FormData.ts +++ b/packages/platform-node/src/Http/Multipart.ts @@ -1,22 +1,22 @@ /** * @since 1.0.0 * - * Also includes exports from [`@effect/platform/Http/FormData`](https://effect-ts.github.io/platform/platform/Http/FormData.ts.html). + * Also includes exports from [`@effect/platform/Http/Multipart`](https://effect-ts.github.io/platform/platform/Http/Multipart.ts.html). */ import type * as FileSystem from "@effect/platform/FileSystem" -import type * as FormData from "@effect/platform/Http/FormData" +import type * as Multipart from "@effect/platform/Http/Multipart" import type * as Path from "@effect/platform/Path" import type * as Effect from "effect/Effect" import type * as Scope from "effect/Scope" import type * as Stream from "effect/Stream" import type { IncomingHttpHeaders } from "node:http" import type { Readable } from "node:stream" -import * as internal from "../internal/http/formData.js" +import * as internal from "../internal/http/multipart.js" /** * @since 1.0.0 */ -export * from "@effect/platform/Http/FormData" +export * from "@effect/platform/Http/Multipart" /** * @since 1.0.0 @@ -25,23 +25,23 @@ export * from "@effect/platform/Http/FormData" export const stream: ( source: Readable, headers: IncomingHttpHeaders -) => Stream.Stream = internal.stream +) => Stream.Stream = internal.stream /** * @since 1.0.0 * @category constructors */ -export const formData: ( +export const persisted: ( source: Readable, headers: IncomingHttpHeaders ) => Effect.Effect< FileSystem.FileSystem | Path.Path | Scope.Scope, - FormData.FormDataError, - FormData.PersistedFormData -> = internal.formData + Multipart.MultipartError, + Multipart.Persisted +> = internal.persisted /** * @since 1.0.0 * @category conversions */ -export const fileToReadable: (file: FormData.File) => Readable = internal.fileToReadable +export const fileToReadable: (file: Multipart.File) => Readable = internal.fileToReadable diff --git a/packages/platform-node/src/HttpServer.ts b/packages/platform-node/src/HttpServer.ts index c5e099ff..ef4edf15 100644 --- a/packages/platform-node/src/HttpServer.ts +++ b/packages/platform-node/src/HttpServer.ts @@ -10,7 +10,7 @@ import * as error from "@effect/platform/Http/ServerError" import * as response from "@effect/platform/Http/ServerResponse" import * as urlParams from "@effect/platform/Http/UrlParams" import * as etag from "./Http/Etag.js" -import * as formData from "./Http/FormData.js" +import * as multipart from "./Http/Multipart.js" import * as server from "./Http/Server.js" import * as request from "./Http/ServerRequest.js" @@ -43,13 +43,6 @@ export { * - Module: `@effect/platform-node/Http/Etag` */ etag, - /** - * @since 1.0.0 - * - * - Docs: [Http/FormData](https://effect-ts.github.io/platform/platform-node/Http/FormData.ts.html) - * - Module: `@effect/platform-node/Http/FormData` - */ - formData, /** * @since 1.0.0 * @@ -64,6 +57,13 @@ export { * - Module: `@effect/platform/Http/Middleware` */ middleware, + /** + * @since 1.0.0 + * + * - Docs: [Http/Multipart](https://effect-ts.github.io/platform/platform-node/Http/Multipart.ts.html) + * - Module: `@effect/platform-node/Http/Multipart` + */ + multipart, /** * @since 1.0.0 * diff --git a/packages/platform-node/src/internal/http/formData.ts b/packages/platform-node/src/internal/http/multipart.ts similarity index 60% rename from packages/platform-node/src/internal/http/formData.ts rename to packages/platform-node/src/internal/http/multipart.ts index e398f146..bf159987 100644 --- a/packages/platform-node/src/internal/http/formData.ts +++ b/packages/platform-node/src/internal/http/multipart.ts @@ -1,4 +1,4 @@ -import * as FormData from "@effect/platform/Http/FormData" +import * as Multipart from "@effect/platform/Http/Multipart" import * as Effect from "effect/Effect" import { pipe } from "effect/Function" import * as Stream from "effect/Stream" @@ -15,12 +15,12 @@ import * as NodeStream from "../stream.js" export const stream = ( source: Readable, headers: IncomingHttpHeaders -): Stream.Stream => +): Stream.Stream => pipe( - FormData.makeConfig(headers as any), + Multipart.makeConfig(headers as any), Effect.map( (config) => - NodeStream.fromReadable(() => { + NodeStream.fromReadable(() => { const parser = MP.make(config) source.pipe(parser) return parser @@ -31,21 +31,21 @@ export const stream = ( ) /** @internal */ -export const formData = ( +export const persisted = ( source: Readable, headers: IncomingHttpHeaders ) => - FormData.formData(stream(source, headers), (path, file) => + Multipart.toPersisted(stream(source, headers), (path, file) => Effect.tryPromise({ try: (signal) => NodeStreamP.pipeline((file as FileImpl).file, NFS.createWriteStream(path), { signal }), - catch: (error) => FormData.FormDataError("InternalError", error) + catch: (error) => Multipart.MultipartError("InternalError", error) })) -const convertPart = (part: MP.Part): FormData.Part => +const convertPart = (part: MP.Part): Multipart.Part => part._tag === "Field" ? new FieldImpl(part.info, part.value) : new FileImpl(part) -class FieldImpl implements FormData.Field { - readonly [FormData.TypeId]: FormData.TypeId +class FieldImpl implements Multipart.Field { + readonly [Multipart.TypeId]: Multipart.TypeId readonly _tag = "Field" readonly key: string readonly contentType: string @@ -55,53 +55,53 @@ class FieldImpl implements FormData.Field { info: PartInfo, value: Uint8Array ) { - this[FormData.TypeId] = FormData.TypeId + this[Multipart.TypeId] = Multipart.TypeId this.key = info.name this.contentType = info.contentType this.value = decodeField(info, value) } } -class FileImpl implements FormData.File { +class FileImpl implements Multipart.File { readonly _tag = "File" - readonly [FormData.TypeId]: FormData.TypeId + readonly [Multipart.TypeId]: Multipart.TypeId readonly key: string readonly name: string readonly contentType: string - readonly content: Stream.Stream + readonly content: Stream.Stream constructor(readonly file: MP.FileStream) { - this[FormData.TypeId] = FormData.TypeId + this[Multipart.TypeId] = Multipart.TypeId this.key = file.info.name this.name = file.filename ?? file.info.name this.contentType = file.info.contentType - this.content = NodeStream.fromReadable(() => file, (error) => FormData.FormDataError("InternalError", error)) + this.content = NodeStream.fromReadable(() => file, (error) => Multipart.MultipartError("InternalError", error)) } } /** @internal */ -export const fileToReadable = (file: FormData.File): Readable => (file as FileImpl).file +export const fileToReadable = (file: Multipart.File): Readable => (file as FileImpl).file -function convertError(error: MultipartError): FormData.FormDataError { +function convertError(error: MultipartError): Multipart.MultipartError { switch (error._tag) { case "ReachedLimit": { switch (error.limit) { case "MaxParts": { - return FormData.FormDataError("TooManyParts", error) + return Multipart.MultipartError("TooManyParts", error) } case "MaxFieldSize": { - return FormData.FormDataError("FieldTooLarge", error) + return Multipart.MultipartError("FieldTooLarge", error) } case "MaxPartSize": { - return FormData.FormDataError("FileTooLarge", error) + return Multipart.MultipartError("FileTooLarge", error) } case "MaxTotalSize": { - return FormData.FormDataError("BodyTooLarge", error) + return Multipart.MultipartError("BodyTooLarge", error) } } } default: { - return FormData.FormDataError("Parse", error) + return Multipart.MultipartError("Parse", error) } } } diff --git a/packages/platform-node/src/internal/http/server.ts b/packages/platform-node/src/internal/http/server.ts index d6bce70d..7a11b0d1 100644 --- a/packages/platform-node/src/internal/http/server.ts +++ b/packages/platform-node/src/internal/http/server.ts @@ -1,10 +1,10 @@ import * as FileSystem from "@effect/platform/FileSystem" import * as App from "@effect/platform/Http/App" -import type * as FormData from "@effect/platform/Http/FormData" import type * as Headers from "@effect/platform/Http/Headers" import * as IncomingMessage from "@effect/platform/Http/IncomingMessage" import type { Method } from "@effect/platform/Http/Method" import * as Middleware from "@effect/platform/Http/Middleware" +import type * as Multipart from "@effect/platform/Http/Multipart" import * as Server from "@effect/platform/Http/Server" import * as Error from "@effect/platform/Http/ServerError" import * as ServerRequest from "@effect/platform/Http/ServerRequest" @@ -24,8 +24,8 @@ import type * as Net from "node:net" import { Readable } from "node:stream" import { pipeline } from "node:stream/promises" import * as NodeSink from "../../Sink.js" -import * as internalFormData from "./formData.js" import { IncomingMessageImpl } from "./incomingMessage.js" +import * as internalMultipart from "./multipart.js" import * as internalPlatform from "./platform.js" /** @internal */ @@ -207,29 +207,29 @@ class ServerRequestImpl extends IncomingMessageImpl implemen return this.headersOverride } - private formDataEffect: + private multipartEffect: | Effect.Effect< Scope.Scope | FileSystem.FileSystem | Path.Path, - FormData.FormDataError, - FormData.PersistedFormData + Multipart.MultipartError, + Multipart.Persisted > | undefined - get formData(): Effect.Effect< + get multipart(): Effect.Effect< Scope.Scope | FileSystem.FileSystem | Path.Path, - FormData.FormDataError, - FormData.PersistedFormData + Multipart.MultipartError, + Multipart.Persisted > { - if (this.formDataEffect) { - return this.formDataEffect + if (this.multipartEffect) { + return this.multipartEffect } - this.formDataEffect = Effect.runSync(Effect.cached( - internalFormData.formData(this.source, this.source.headers) + this.multipartEffect = Effect.runSync(Effect.cached( + internalMultipart.persisted(this.source, this.source.headers) )) - return this.formDataEffect + return this.multipartEffect } - get formDataStream(): Stream.Stream { - return internalFormData.stream(this.source, this.source.headers) + get multipartStream(): Stream.Stream { + return internalMultipart.stream(this.source, this.source.headers) } toString(): string { diff --git a/packages/platform-node/test/HttpServer.test.ts b/packages/platform-node/test/HttpServer.test.ts index 2b4b3560..adc57217 100644 --- a/packages/platform-node/test/HttpServer.test.ts +++ b/packages/platform-node/test/HttpServer.test.ts @@ -82,7 +82,7 @@ describe("HttpServer", () => { "/upload", Effect.gen(function*(_) { const request = yield* _(Http.request.ServerRequest) - const formData = yield* _(request.formData) + const formData = yield* _(request.multipart) const part = formData.file assert(typeof part !== "string") const file = part[0] @@ -103,15 +103,15 @@ describe("HttpServer", () => { expect(result).toEqual({ ok: true }) }).pipe(Effect.scoped, runPromise)) - it("schemaFormData", () => + it("schemaBodyForm", () => Effect.gen(function*(_) { yield* _( Http.router.empty, Http.router.post( "/upload", Effect.gen(function*(_) { - const files = yield* _(Http.request.schemaFormData(Schema.struct({ - file: Http.formData.filesSchema, + const files = yield* _(Http.request.schemaBodyForm(Schema.struct({ + file: Http.multipart.filesSchema, test: Schema.string }))) expect(files).toHaveProperty("file") @@ -140,16 +140,16 @@ describe("HttpServer", () => { "/upload", Effect.gen(function*(_) { const request = yield* _(Http.request.ServerRequest) - yield* _(request.formData) + yield* _(request.multipart) return Http.response.empty() }).pipe(Effect.scoped) ), - Effect.catchTag("FormDataError", (error) => + Effect.catchTag("MultipartError", (error) => error.reason === "FileTooLarge" ? Http.response.empty({ status: 413 }) : Effect.fail(error)), Http.server.serveEffect(), - Http.formData.withMaxFileSize(Option.some(100)) + Http.multipart.withMaxFileSize(Option.some(100)) ) const client = yield* _(makeClient) const formData = new FormData() @@ -169,16 +169,16 @@ describe("HttpServer", () => { "/upload", Effect.gen(function*(_) { const request = yield* _(Http.request.ServerRequest) - yield* _(request.formData) + yield* _(request.multipart) return Http.response.empty() }).pipe(Effect.scoped) ), - Effect.catchTag("FormDataError", (error) => + Effect.catchTag("MultipartError", (error) => error.reason === "FieldTooLarge" ? Http.response.empty({ status: 413 }) : Effect.fail(error)), Http.server.serveEffect(), - Http.formData.withMaxFieldSize(100) + Http.multipart.withMaxFieldSize(100) ) const client = yield* _(makeClient) const formData = new FormData() diff --git a/packages/platform/src/Http/FormData.ts b/packages/platform/src/Http/Multipart.ts similarity index 84% rename from packages/platform/src/Http/FormData.ts rename to packages/platform/src/Http/Multipart.ts index 978a2805..802eecae 100644 --- a/packages/platform/src/Http/FormData.ts +++ b/packages/platform/src/Http/Multipart.ts @@ -13,7 +13,7 @@ import type * as Scope from "effect/Scope" import type * as Stream from "effect/Stream" import type * as Multipasta from "multipasta" import type * as FileSystem from "../FileSystem.js" -import * as internal from "../internal/http/formData.js" +import * as internal from "../internal/http/multipart.js" import type * as Path from "../Path.js" /** @@ -68,7 +68,7 @@ export interface File extends Part.Proto { readonly key: string readonly name: string readonly contentType: string - readonly content: Stream.Stream + readonly content: Stream.Stream } /** @@ -87,7 +87,7 @@ export interface PersistedFile extends Part.Proto { * @since 1.0.0 * @category models */ -export interface PersistedFormData { +export interface Persisted { readonly [key: string]: ReadonlyArray | string } @@ -107,9 +107,9 @@ export type ErrorTypeId = typeof ErrorTypeId * @since 1.0.0 * @category errors */ -export interface FormDataError extends Data.Case { +export interface MultipartError extends Data.Case { readonly [ErrorTypeId]: ErrorTypeId - readonly _tag: "FormDataError" + readonly _tag: "MultipartError" readonly reason: "FileTooLarge" | "FieldTooLarge" | "BodyTooLarge" | "TooManyParts" | "InternalError" | "Parse" readonly error: unknown } @@ -118,10 +118,10 @@ export interface FormDataError extends Data.Case { * @since 1.0.0 * @category errors */ -export const FormDataError: ( - reason: FormDataError["reason"], +export const MultipartError: ( + reason: MultipartError["reason"], error: unknown -) => FormDataError = internal.FormDataError +) => MultipartError = internal.MultipartError /** * @since 1.0.0 @@ -203,17 +203,17 @@ export const filesSchema: Schema.Schema, ReadonlyAr export const schemaJson: ( schema: Schema.Schema ) => { - (field: string): (formData: PersistedFormData) => Effect.Effect - (formData: PersistedFormData, field: string): Effect.Effect + (field: string): (persisted: Persisted) => Effect.Effect + (persisted: Persisted, field: string): Effect.Effect } = internal.schemaJson /** * @since 1.0.0 * @category schema */ -export const schemaPersisted: ( +export const schemaPersisted: ( schema: Schema.Schema -) => (formData: PersistedFormData) => Effect.Effect = internal.schemaPersisted +) => (persisted: Persisted) => Effect.Effect = internal.schemaPersisted /** * @since 1.0.0 @@ -222,7 +222,7 @@ export const schemaPersisted: ( export const makeChannel: ( headers: Record, bufferSize?: number -) => Channel.Channel, unknown, FormDataError | IE, Chunk.Chunk, unknown> = +) => Channel.Channel, unknown, MultipartError | IE, Chunk.Chunk, unknown> = internal.makeChannel /** @@ -236,8 +236,7 @@ export const makeConfig: (headers: Record) => Effect.Effect, - writeFile?: (path: string, file: File) => Effect.Effect -) => Effect.Effect = - internal.formData +export const toPersisted: ( + stream: Stream.Stream, + writeFile?: (path: string, file: File) => Effect.Effect +) => Effect.Effect = internal.toPersisted diff --git a/packages/platform/src/Http/ServerRequest.ts b/packages/platform/src/Http/ServerRequest.ts index c936e149..d747c2da 100644 --- a/packages/platform/src/Http/ServerRequest.ts +++ b/packages/platform/src/Http/ServerRequest.ts @@ -10,10 +10,10 @@ import type * as Stream from "effect/Stream" import type * as FileSystem from "../FileSystem.js" import * as internal from "../internal/http/serverRequest.js" import type * as Path from "../Path.js" -import type * as FormData from "./FormData.js" import type * as Headers from "./Headers.js" import type * as IncomingMessage from "./IncomingMessage.js" import type { Method } from "./Method.js" +import type * as Multipart from "./Multipart.js" import type * as Error from "./ServerError.js" export { @@ -47,12 +47,12 @@ export interface ServerRequest extends IncomingMessage.IncomingMessage - readonly formDataStream: Stream.Stream + readonly multipartStream: Stream.Stream readonly modify: ( options: { @@ -73,11 +73,11 @@ export const ServerRequest: Context.Tag = internal * @since 1.0.0 * @category accessors */ -export const persistedFormData: Effect.Effect< +export const persistedMultipart: Effect.Effect< Scope.Scope | FileSystem.FileSystem | Path.Path | ServerRequest, - FormData.FormDataError, + Multipart.MultipartError, unknown -> = internal.persistedFormData +> = internal.multipartPersisted /** * @since 1.0.0 @@ -95,6 +95,18 @@ export const schemaBodyJson: ( schema: Schema.Schema ) => Effect.Effect = internal.schemaBodyJson +/** + * @since 1.0.0 + * @category schema + */ +export const schemaBodyForm: ( + schema: Schema.Schema +) => Effect.Effect< + ServerRequest | Scope.Scope | FileSystem.FileSystem | Path.Path, + Multipart.MultipartError | Error.RequestError | ParseResult.ParseError, + A +> = internal.schemaBodyForm + /** * @since 1.0.0 * @category schema @@ -107,27 +119,27 @@ export const schemaBodyUrlParams: >, A * @since 1.0.0 * @category schema */ -export const schemaFormData: ( +export const schemaBodyMultipart: ( schema: Schema.Schema ) => Effect.Effect< ServerRequest | Scope.Scope | FileSystem.FileSystem | Path.Path, - FormData.FormDataError | ParseResult.ParseError, + Multipart.MultipartError | ParseResult.ParseError, A -> = internal.schemaFormData +> = internal.schemaBodyMultipart /** * @since 1.0.0 * @category schema */ -export const schemaFormDataJson: ( +export const schemaBodyMultipartJson: ( schema: Schema.Schema ) => ( field: string ) => Effect.Effect< ServerRequest | Scope.Scope | FileSystem.FileSystem | Path.Path, - Error.RequestError | FormData.FormDataError | ParseResult.ParseError, + Error.RequestError | Multipart.MultipartError | ParseResult.ParseError, A -> = internal.schemaFormDataJson +> = internal.schemaBodyMultipartJson /** * @since 1.0.0 diff --git a/packages/platform/src/HttpServer.ts b/packages/platform/src/HttpServer.ts index 1fc55198..989489c9 100644 --- a/packages/platform/src/HttpServer.ts +++ b/packages/platform/src/HttpServer.ts @@ -3,9 +3,9 @@ */ import * as app from "./Http/App.js" import * as body from "./Http/Body.js" -import * as formData from "./Http/FormData.js" import * as headers from "./Http/Headers.js" import * as middleware from "./Http/Middleware.js" +import * as multipart from "./Http/Multipart.js" import * as router from "./Http/Router.js" import * as error from "./Http/ServerError.js" import * as request from "./Http/ServerRequest.js" @@ -34,13 +34,6 @@ export { * - Module: `@effect/platform/Http/ServerError` */ error, - /** - * @since 1.0.0 - * - * - Docs: [Http/FormData](https://effect-ts.github.io/platform/platform/Http/FormData.html) - * - Module: `@effect/platform/Http/FormData` - */ - formData, /** * @since 1.0.0 * @@ -55,6 +48,13 @@ export { * - Module: `@effect/platform/Http/Middleware` */ middleware, + /** + * @since 1.0.0 + * + * - Docs: [Http/Multipart](https://effect-ts.github.io/platform/platform/Http/Multipart.html) + * - Module: `@effect/platform/Http/Multipart` + */ + multipart, /** * @since 1.0.0 * diff --git a/packages/platform/src/internal/http/formData.ts b/packages/platform/src/internal/http/multipart.ts similarity index 72% rename from packages/platform/src/internal/http/formData.ts rename to packages/platform/src/internal/http/multipart.ts index f98efc6a..a2a059a9 100644 --- a/packages/platform/src/internal/http/formData.ts +++ b/packages/platform/src/internal/http/multipart.ts @@ -16,34 +16,34 @@ import type * as AsyncInput from "effect/SingleProducerAsyncInput" import * as Stream from "effect/Stream" import * as MP from "multipasta" import * as FileSystem from "../../FileSystem.js" -import type * as FormData from "../../Http/FormData.js" import * as IncomingMessage from "../../Http/IncomingMessage.js" +import type * as Multipart from "../../Http/Multipart.js" import * as Path from "../../Path.js" /** @internal */ -export const TypeId: FormData.TypeId = Symbol.for("@effect/platform/Http/FormData") as FormData.TypeId +export const TypeId: Multipart.TypeId = Symbol.for("@effect/platform/Http/Multipart") as Multipart.TypeId /** @internal */ -export const ErrorTypeId: FormData.ErrorTypeId = Symbol.for( - "@effect/platform/Http/FormData/FormDataError" -) as FormData.ErrorTypeId +export const ErrorTypeId: Multipart.ErrorTypeId = Symbol.for( + "@effect/platform/Http/Multipart/MultipartError" +) as Multipart.ErrorTypeId /** @internal */ -export const FormDataError = (reason: FormData.FormDataError["reason"], error: unknown): FormData.FormDataError => +export const MultipartError = (reason: Multipart.MultipartError["reason"], error: unknown): Multipart.MultipartError => Data.struct({ [ErrorTypeId]: ErrorTypeId, - _tag: "FormDataError", + _tag: "MultipartError", reason, error }) /** @internal */ -export const isField = (u: unknown): u is FormData.Field => +export const isField = (u: unknown): u is Multipart.Field => Predicate.hasProperty(u, TypeId) && Predicate.isTagged(u, "Field") /** @internal */ export const maxParts: FiberRef.FiberRef> = globalValue( - "@effect/platform/Http/FormData/maxParts", + "@effect/platform/Http/Multipart/maxParts", () => FiberRef.unsafeMake(Option.none()) ) @@ -55,7 +55,7 @@ export const withMaxParts = dual< /** @internal */ export const maxFieldSize: FiberRef.FiberRef = globalValue( - "@effect/platform/Http/FormData/maxFieldSize", + "@effect/platform/Http/Multipart/maxFieldSize", () => FiberRef.unsafeMake(FileSystem.Size(10 * 1024 * 1024)) ) @@ -67,7 +67,7 @@ export const withMaxFieldSize = dual< /** @internal */ export const maxFileSize: FiberRef.FiberRef> = globalValue( - "@effect/platform/Http/FormData/maxFileSize", + "@effect/platform/Http/Multipart/maxFileSize", () => FiberRef.unsafeMake(Option.none()) ) @@ -79,7 +79,7 @@ export const withMaxFileSize = dual< /** @internal */ export const fieldMimeTypes: FiberRef.FiberRef> = globalValue( - "@effect/platform/Http/FormData/fieldMimeTypes", + "@effect/platform/Http/Multipart/fieldMimeTypes", () => FiberRef.unsafeMake>(Chunk.make("application/json")) ) @@ -90,56 +90,58 @@ export const withFieldMimeTypes = dual< >(2, (effect, mimeTypes) => Effect.locally(effect, fieldMimeTypes, Chunk.fromIterable(mimeTypes))) /** @internal */ -export const filesSchema: Schema.Schema, ReadonlyArray> = - Schema - .array( - pipe( - Schema.object, - Schema.filter( - (file): file is FormData.PersistedFile => TypeId in file && "_tag" in file && file._tag === "PersistedFile" - ) - ) as any as Schema.Schema - ) +export const filesSchema: Schema.Schema< + ReadonlyArray, + ReadonlyArray +> = Schema + .array( + pipe( + Schema.object, + Schema.filter( + (file): file is Multipart.PersistedFile => TypeId in file && "_tag" in file && file._tag === "PersistedFile" + ) + ) as any as Schema.Schema + ) /** @internal */ -export const schemaPersisted = ( +export const schemaPersisted = ( schema: Schema.Schema ) => { const parse = Schema.parse(schema) - return (formData: FormData.PersistedFormData) => parse(formData) + return (persisted: Multipart.Persisted) => parse(persisted) } /** @internal */ export const schemaJson = (schema: Schema.Schema): { ( field: string - ): (formData: FormData.PersistedFormData) => Effect.Effect + ): (persisted: Multipart.Persisted) => Effect.Effect ( - formData: FormData.PersistedFormData, + persisted: Multipart.Persisted, field: string - ): Effect.Effect + ): Effect.Effect } => { const parse = Schema.parse(schema) return dual< ( field: string ) => ( - formData: FormData.PersistedFormData - ) => Effect.Effect, + persisted: Multipart.Persisted + ) => Effect.Effect, ( - formData: FormData.PersistedFormData, + persisted: Multipart.Persisted, field: string - ) => Effect.Effect - >(2, (formData, field) => + ) => Effect.Effect + >(2, (persisted, field) => pipe( - Effect.succeed(formData[field]), + Effect.succeed(persisted[field]), Effect.filterOrFail( isField, - () => FormDataError("Parse", `schemaJson: was not a field`) + () => MultipartError("Parse", `schemaJson: was not a field`) ), Effect.tryMap({ try: (field) => JSON.parse(field.value), - catch: (error) => FormDataError("Parse", `schemaJson: field was not valid json: ${error}`) + catch: (error) => MultipartError("Parse", `schemaJson: field was not valid json: ${error}`) }), Effect.flatMap(parse) )) @@ -181,8 +183,8 @@ export const makeChannel = ( IE, Chunk.Chunk, unknown, - FormData.FormDataError | IE, - Chunk.Chunk, + Multipart.MultipartError | IE, + Chunk.Chunk, unknown > => Channel.acquireUseRelease( @@ -202,13 +204,13 @@ const makeFromQueue = ( IE, Chunk.Chunk, unknown, - IE | FormData.FormDataError, - Chunk.Chunk, + IE | Multipart.MultipartError, + Chunk.Chunk, unknown > => Channel.suspend(() => { - let error = Option.none>() - let partsBuffer: Array = [] + let error = Option.none>() + let partsBuffer: Array = [] let partsFinished = false const input: AsyncInput.AsyncInputProducer, unknown> = { @@ -293,8 +295,8 @@ const makeFromQueue = ( unknown, unknown, unknown, - IE | FormData.FormDataError, - Chunk.Chunk, + IE | Multipart.MultipartError, + Chunk.Chunk, void > = Channel.suspend(() => { if (error._tag === "Some") { @@ -308,32 +310,32 @@ const makeFromQueue = ( return Channel.embedInput(partsChannel, input) }) -function convertError(error: MP.MultipartError): FormData.FormDataError { +function convertError(error: MP.MultipartError): Multipart.MultipartError { switch (error._tag) { case "ReachedLimit": { switch (error.limit) { case "MaxParts": { - return FormDataError("TooManyParts", error) + return MultipartError("TooManyParts", error) } case "MaxFieldSize": { - return FormDataError("FieldTooLarge", error) + return MultipartError("FieldTooLarge", error) } case "MaxPartSize": { - return FormDataError("FileTooLarge", error) + return MultipartError("FileTooLarge", error) } case "MaxTotalSize": { - return FormDataError("BodyTooLarge", error) + return MultipartError("BodyTooLarge", error) } } } default: { - return FormDataError("Parse", error) + return MultipartError("Parse", error) } } } -class FieldImpl implements FormData.Field { - readonly [TypeId]: FormData.TypeId +class FieldImpl implements Multipart.Field { + readonly [TypeId]: Multipart.TypeId readonly _tag = "Field" constructor( @@ -345,13 +347,13 @@ class FieldImpl implements FormData.Field { } } -class FileImpl implements FormData.File { +class FileImpl implements Multipart.File { readonly _tag = "File" - readonly [TypeId]: FormData.TypeId + readonly [TypeId]: Multipart.TypeId readonly key: string readonly name: string readonly contentType: string - readonly content: Stream.Stream + readonly content: Stream.Stream constructor( info: MP.PartInfo, @@ -365,21 +367,21 @@ class FileImpl implements FormData.File { } } -const defaultWriteFile = (path: string, file: FormData.File) => +const defaultWriteFile = (path: string, file: Multipart.File) => Effect.flatMap( FileSystem.FileSystem, (fs) => Effect.mapError( Stream.run(file.content, fs.sink(path)), - (error) => FormDataError("InternalError", error) + (error) => MultipartError("InternalError", error) ) ) /** @internal */ -export const formData = ( - stream: Stream.Stream, +export const toPersisted = ( + stream: Stream.Stream, writeFile = defaultWriteFile -): Effect.Effect => +): Effect.Effect => pipe( Effect.Do, Effect.bind("fs", () => FileSystem.FileSystem), @@ -388,18 +390,18 @@ export const formData = ( Effect.flatMap(({ dir, path: path_ }) => Stream.runFoldEffect( stream, - Object.create(null) as Record | string>, - (formData, part) => { + Object.create(null) as Record | string>, + (persisted, part) => { if (part._tag === "Field") { - formData[part.key] = part.value - return Effect.succeed(formData) + persisted[part.key] = part.value + return Effect.succeed(persisted) } const file = part const path = path_.join(dir, path_.basename(file.name).slice(-128)) - if (!Array.isArray(formData[part.key])) { - formData[part.key] = [] + if (!Array.isArray(persisted[part.key])) { + persisted[part.key] = [] } - ;(formData[part.key] as Array).push( + ;(persisted[part.key] as Array).push( new PersistedFileImpl( file.key, file.name, @@ -407,18 +409,18 @@ export const formData = ( path ) ) - return Effect.as(writeFile(path, file), formData) + return Effect.as(writeFile(path, file), persisted) } ) ), Effect.catchTags({ - SystemError: (err) => Effect.fail(FormDataError("InternalError", err)), - BadArgument: (err) => Effect.fail(FormDataError("InternalError", err)) + SystemError: (err) => Effect.fail(MultipartError("InternalError", err)), + BadArgument: (err) => Effect.fail(MultipartError("InternalError", err)) }) ) -class PersistedFileImpl implements FormData.PersistedFile { - readonly [TypeId]: FormData.TypeId +class PersistedFileImpl implements Multipart.PersistedFile { + readonly [TypeId]: Multipart.TypeId readonly _tag = "PersistedFile" constructor( diff --git a/packages/platform/src/internal/http/serverRequest.ts b/packages/platform/src/internal/http/serverRequest.ts index e256912c..ca408f82 100644 --- a/packages/platform/src/internal/http/serverRequest.ts +++ b/packages/platform/src/internal/http/serverRequest.ts @@ -1,3 +1,4 @@ +import type * as ParseResult from "@effect/schema/ParseResult" import type * as Schema from "@effect/schema/Schema" import * as Context from "effect/Context" import * as Effect from "effect/Effect" @@ -5,10 +6,10 @@ import * as Option from "effect/Option" import type * as Scope from "effect/Scope" import * as Stream from "effect/Stream" import type * as FileSystem from "../../FileSystem.js" -import * as FormData from "../../Http/FormData.js" import * as Headers from "../../Http/Headers.js" import * as IncomingMessage from "../../Http/IncomingMessage.js" import type { Method } from "../../Http/Method.js" +import * as Multipart from "../../Http/Multipart.js" import * as Error from "../../Http/ServerError.js" import type * as ServerRequest from "../../Http/ServerRequest.js" import * as UrlParams from "../../Http/UrlParams.js" @@ -21,7 +22,7 @@ export const TypeId: ServerRequest.TypeId = Symbol.for("@effect/platform/Http/Se export const serverRequestTag = Context.Tag(TypeId) /** @internal */ -export const persistedFormData = Effect.flatMap(serverRequestTag, (request) => request.formData) +export const multipartPersisted = Effect.flatMap(serverRequestTag, (request) => request.multipart) /** @internal */ export const schemaHeaders = >, A>(schema: Schema.Schema) => { @@ -35,6 +36,24 @@ export const schemaBodyJson = (schema: Schema.Schema) => { return Effect.flatMap(serverRequestTag, parse) } +/** @internal */ +export const schemaBodyForm = ( + schema: Schema.Schema +) => { + const parseMultipart = Multipart.schemaPersisted(schema) + const parseUrlParams = IncomingMessage.schemaBodyUrlParams(schema as Schema.Schema) + return Effect.flatMap(serverRequestTag, (request): Effect.Effect< + ServerRequest.ServerRequest | Scope.Scope | FileSystem.FileSystem | Path.Path, + Multipart.MultipartError | ParseResult.ParseError | Error.RequestError, + A + > => { + if (request.headers["content-type"]?.trim().toLowerCase().startsWith("multipart/form-data")) { + return Effect.flatMap(request.multipart, parseMultipart) + } + return parseUrlParams(request) + }) +} + /** @internal */ export const schemaBodyUrlParams = >, A>(schema: Schema.Schema) => { const parse = IncomingMessage.schemaBodyUrlParams(schema) @@ -42,24 +61,24 @@ export const schemaBodyUrlParams = >, } /** @internal */ -export const schemaFormData = ( +export const schemaBodyMultipart = ( schema: Schema.Schema ) => { - const parse = FormData.schemaPersisted(schema) - return Effect.flatMap(persistedFormData, parse) + const parse = Multipart.schemaPersisted(schema) + return Effect.flatMap(multipartPersisted, parse) } /** @internal */ -export const schemaFormDataJson = (schema: Schema.Schema) => { - const parse = FormData.schemaJson(schema) +export const schemaBodyMultipartJson = (schema: Schema.Schema) => { + const parse = Multipart.schemaJson(schema) return (field: string) => Effect.flatMap(serverRequestTag, (request) => Effect.flatMap( - request.formData, - (formData) => + request.multipart, + (persisted) => Effect.catchTag( - parse(formData, field), - "FormDataError", + parse(persisted, field), + "MultipartError", (error) => Effect.fail( Error.RequestError({ @@ -175,31 +194,31 @@ class ServerRequestImpl implements ServerRequest.ServerRequest { })) } - private formDataEffect: + private multipartEffect: | Effect.Effect< Scope.Scope | FileSystem.FileSystem | Path.Path, - FormData.FormDataError, - FormData.PersistedFormData + Multipart.MultipartError, + Multipart.Persisted > | undefined - get formData(): Effect.Effect< + get multipart(): Effect.Effect< Scope.Scope | FileSystem.FileSystem | Path.Path, - FormData.FormDataError, - FormData.PersistedFormData + Multipart.MultipartError, + Multipart.Persisted > { - if (this.formDataEffect) { - return this.formDataEffect + if (this.multipartEffect) { + return this.multipartEffect } - this.formDataEffect = Effect.runSync(Effect.cached( - FormData.formData(this.formDataStream) + this.multipartEffect = Effect.runSync(Effect.cached( + Multipart.toPersisted(this.multipartStream) )) - return this.formDataEffect + return this.multipartEffect } - get formDataStream(): Stream.Stream { + get multipartStream(): Stream.Stream { return Stream.pipeThroughChannel( - Stream.mapError(this.stream, (error) => FormData.FormDataError("InternalError", error)), - FormData.makeChannel(this.headers) + Stream.mapError(this.stream, (error) => Multipart.MultipartError("InternalError", error)), + Multipart.makeChannel(this.headers) ) } diff --git a/packages/platform/src/internal/http/serverResponse.ts b/packages/platform/src/internal/http/serverResponse.ts index aa4b81db..86530470 100644 --- a/packages/platform/src/internal/http/serverResponse.ts +++ b/packages/platform/src/internal/http/serverResponse.ts @@ -176,7 +176,10 @@ export const raw = (body: unknown, options?: ServerResponse.Options): ServerResp ) /** @internal */ -export const formData = (body: FormData, options?: ServerResponse.Options.WithContent): ServerResponse.ServerResponse => +export const formData = ( + body: FormData, + options?: ServerResponse.Options.WithContent +): ServerResponse.ServerResponse => new ServerResponseImpl( options?.status ?? 200, options?.statusText, diff --git a/packages/platform/test/Http/FormData.test.ts b/packages/platform/test/Http/Multipart.test.ts similarity index 85% rename from packages/platform/test/Http/FormData.test.ts rename to packages/platform/test/Http/Multipart.test.ts index 0b35efb3..ea3aacae 100644 --- a/packages/platform/test/Http/FormData.test.ts +++ b/packages/platform/test/Http/Multipart.test.ts @@ -1,8 +1,8 @@ -import * as FormData from "@effect/platform/Http/FormData" +import * as Multipart from "@effect/platform/Http/Multipart" import { Chunk, Effect, identity, Stream } from "effect" import { assert, describe, test } from "vitest" -describe("FormData", () => { +describe("Multipart", () => { test("it parses", () => Effect.gen(function*(_) { const data = new globalThis.FormData() @@ -13,7 +13,7 @@ describe("FormData", () => { const parts = yield* _( Stream.fromReadableStream(() => response.body!, identity), - Stream.pipeThroughChannel(FormData.makeChannel(Object.fromEntries(response.headers))), + Stream.pipeThroughChannel(Multipart.makeChannel(Object.fromEntries(response.headers))), Stream.mapEffect((part) => { return Effect.unified( part._tag === "File" ?