From 6e18090db4686cd5564ab9dc3d8771d7b3ad97fa Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 14 Nov 2023 10:30:29 +1300 Subject: [PATCH] use multipasta for multipart parsing (#250) --- .changeset/gold-knives-carry.md | 8 + docs/platform-bun/Worker.ts.md | 2 +- docs/platform-bun/WorkerRunner.ts.md | 2 +- docs/platform-node/Http/FormData.ts.md | 16 +- docs/platform-node/Worker.ts.md | 2 +- docs/platform-node/WorkerRunner.ts.md | 2 +- docs/platform-node/index.ts.md | 13 + docs/platform/Effectify.ts.md | 396 +++++++++--------- docs/platform/Error.ts.md | 2 +- docs/platform/Http/FormData.ts.md | 148 ++++--- docs/platform/Http/ServerRequest.ts.md | 16 +- docs/platform/Terminal.ts.md | 4 + docs/platform/Worker.ts.md | 2 +- docs/platform/WorkerError.ts.md | 2 +- docs/platform/WorkerRunner.ts.md | 2 +- docs/platform/index.ts.md | 13 + package.json | 2 +- packages/platform-browser/src/Worker.ts | 2 +- packages/platform-browser/src/WorkerRunner.ts | 2 +- packages/platform-bun/package.json | 2 +- packages/platform-bun/src/Worker.ts | 2 +- packages/platform-bun/src/WorkerRunner.ts | 2 +- .../platform-bun/src/internal/http/server.ts | 4 +- packages/platform-node/package.json | 9 +- packages/platform-node/src/Http/FormData.ts | 13 +- packages/platform-node/src/Worker.ts | 2 +- packages/platform-node/src/WorkerRunner.ts | 2 +- .../src/internal/http/formData.ts | 207 ++++----- .../platform-node/src/internal/http/server.ts | 4 +- .../platform-node/test/HttpServer.test.ts | 12 +- packages/platform/package.json | 3 +- packages/platform/src/Http/FormData.ts | 103 +++-- packages/platform/src/Http/ServerRequest.ts | 14 +- packages/platform/src/WorkerRunner.ts | 2 +- .../platform/src/internal/http/formData.ts | 382 ++++++++++++++--- .../src/internal/http/serverRequest.ts | 14 +- packages/platform/src/internal/worker.ts | 3 +- packages/platform/test/Http/FormData.test.ts | 36 ++ pnpm-lock.yaml | 121 +++--- 39 files changed, 955 insertions(+), 618 deletions(-) create mode 100644 .changeset/gold-knives-carry.md create mode 100644 packages/platform/test/Http/FormData.test.ts diff --git a/.changeset/gold-knives-carry.md b/.changeset/gold-knives-carry.md new file mode 100644 index 00000000..d4ef1989 --- /dev/null +++ b/.changeset/gold-knives-carry.md @@ -0,0 +1,8 @@ +--- +"@effect/platform-browser": minor +"@effect/platform-node": minor +"@effect/platform-bun": minor +"@effect/platform": minor +--- + +updated FormData model and apis diff --git a/docs/platform-bun/Worker.ts.md b/docs/platform-bun/Worker.ts.md index 77aeed13..2378a4e8 100644 --- a/docs/platform-bun/Worker.ts.md +++ b/docs/platform-bun/Worker.ts.md @@ -1,6 +1,6 @@ --- title: Worker.ts -nav_order: 18 +nav_order: 19 parent: "@effect/platform-bun" --- diff --git a/docs/platform-bun/WorkerRunner.ts.md b/docs/platform-bun/WorkerRunner.ts.md index f9518200..864430a5 100644 --- a/docs/platform-bun/WorkerRunner.ts.md +++ b/docs/platform-bun/WorkerRunner.ts.md @@ -1,6 +1,6 @@ --- title: WorkerRunner.ts -nav_order: 19 +nav_order: 20 parent: "@effect/platform-bun" --- diff --git a/docs/platform-node/Http/FormData.ts.md b/docs/platform-node/Http/FormData.ts.md index d1f6c56e..61f456ea 100644 --- a/docs/platform-node/Http/FormData.ts.md +++ b/docs/platform-node/Http/FormData.ts.md @@ -17,6 +17,8 @@ Also includes exports from [`@effect/platform/Http/FormData`](https://effect-ts. - [constructors](#constructors) - [formData](#formdata) - [stream](#stream) +- [conversions](#conversions) + - [fileToReadable](#filetoreadable) - [exports](#exports) - [From "@effect/platform/Http/FormData"](#from-effectplatformhttpformdata) @@ -32,7 +34,7 @@ Also includes exports from [`@effect/platform/Http/FormData`](https://effect-ts. export declare const formData: ( source: Readable, headers: IncomingHttpHeaders -) => Effect.Effect +) => Effect.Effect ``` Added in v1.0.0 @@ -50,6 +52,18 @@ export declare const stream: ( Added in v1.0.0 +# conversions + +## fileToReadable + +**Signature** + +```ts +export declare const fileToReadable: (file: FormData.File) => Readable +``` + +Added in v1.0.0 + # exports ## From "@effect/platform/Http/FormData" diff --git a/docs/platform-node/Worker.ts.md b/docs/platform-node/Worker.ts.md index 9f9469d7..1675af00 100644 --- a/docs/platform-node/Worker.ts.md +++ b/docs/platform-node/Worker.ts.md @@ -1,6 +1,6 @@ --- title: Worker.ts -nav_order: 21 +nav_order: 22 parent: "@effect/platform-node" --- diff --git a/docs/platform-node/WorkerRunner.ts.md b/docs/platform-node/WorkerRunner.ts.md index afaf30ca..9abb012e 100644 --- a/docs/platform-node/WorkerRunner.ts.md +++ b/docs/platform-node/WorkerRunner.ts.md @@ -1,6 +1,6 @@ --- title: WorkerRunner.ts -nav_order: 22 +nav_order: 23 parent: "@effect/platform-node" --- diff --git a/docs/platform-node/index.ts.md b/docs/platform-node/index.ts.md index 21d558de..05f68621 100644 --- a/docs/platform-node/index.ts.md +++ b/docs/platform-node/index.ts.md @@ -26,6 +26,7 @@ Added in v1.0.0 - [From "./Runtime.js"](#from-runtimejs) - [From "./Sink.js"](#from-sinkjs) - [From "./Stream.js"](#from-streamjs) + - [From "./Terminal.js"](#from-terminaljs) - [From "./Worker.js"](#from-workerjs) - [From "./WorkerRunner.js"](#from-workerrunnerjs) @@ -191,6 +192,18 @@ export * as Stream from "./Stream.js" Added in v1.0.0 +## From "./Terminal.js" + +Re-exports all named exports from the "./Terminal.js" module as `Terminal`. + +**Signature** + +```ts +export * as Terminal from "./Terminal.js" +``` + +Added in v1.0.0 + ## From "./Worker.js" Re-exports all named exports from the "./Worker.js" module as `Worker`. diff --git a/docs/platform/Effectify.ts.md b/docs/platform/Effectify.ts.md index 90e3e29b..f66025c6 100644 --- a/docs/platform/Effectify.ts.md +++ b/docs/platform/Effectify.ts.md @@ -41,78 +41,78 @@ export type EffectifyError = T extends { } ? NonNullable : T extends { - (...args: ArgsWithCallback): infer _R1 - (...args: ArgsWithCallback): infer _R2 - (...args: ArgsWithCallback): infer _R3 - (...args: ArgsWithCallback): infer _R4 - (...args: ArgsWithCallback): infer _R5 - (...args: ArgsWithCallback): infer _R6 - (...args: ArgsWithCallback): infer _R7 - (...args: ArgsWithCallback): infer _R8 - (...args: ArgsWithCallback): infer _R9 - } - ? NonNullable - : T extends { - (...args: ArgsWithCallback): infer _R1 - (...args: ArgsWithCallback): infer _R2 - (...args: ArgsWithCallback): infer _R3 - (...args: ArgsWithCallback): infer _R4 - (...args: ArgsWithCallback): infer _R5 - (...args: ArgsWithCallback): infer _R6 - (...args: ArgsWithCallback): infer _R7 - (...args: ArgsWithCallback): infer _R8 - } - ? NonNullable - : T extends { - (...args: ArgsWithCallback): infer _R1 - (...args: ArgsWithCallback): infer _R2 - (...args: ArgsWithCallback): infer _R3 - (...args: ArgsWithCallback): infer _R4 - (...args: ArgsWithCallback): infer _R5 - (...args: ArgsWithCallback): infer _R6 - (...args: ArgsWithCallback): infer _R7 - } - ? NonNullable - : T extends { - (...args: ArgsWithCallback): infer _R1 - (...args: ArgsWithCallback): infer _R2 - (...args: ArgsWithCallback): infer _R3 - (...args: ArgsWithCallback): infer _R4 - (...args: ArgsWithCallback): infer _R5 - (...args: ArgsWithCallback): infer _R6 - } - ? NonNullable - : T extends { - (...args: ArgsWithCallback): infer _R1 - (...args: ArgsWithCallback): infer _R2 - (...args: ArgsWithCallback): infer _R3 - (...args: ArgsWithCallback): infer _R4 - (...args: ArgsWithCallback): infer _R5 - } - ? NonNullable - : T extends { - (...args: ArgsWithCallback): infer _R1 - (...args: ArgsWithCallback): infer _R2 - (...args: ArgsWithCallback): infer _R3 - (...args: ArgsWithCallback): infer _R4 - } - ? NonNullable - : T extends { - (...args: ArgsWithCallback): infer _R1 - (...args: ArgsWithCallback): infer _R2 - (...args: ArgsWithCallback): infer _R3 - } - ? NonNullable - : T extends { - (...args: ArgsWithCallback): infer _R1 - (...args: ArgsWithCallback): infer _R2 - } - ? NonNullable - : T extends { - (...args: ArgsWithCallback): infer _R1 - } - ? NonNullable - : never + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + (...args: ArgsWithCallback): infer _R5 + (...args: ArgsWithCallback): infer _R6 + (...args: ArgsWithCallback): infer _R7 + (...args: ArgsWithCallback): infer _R8 + (...args: ArgsWithCallback): infer _R9 + } + ? NonNullable + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + (...args: ArgsWithCallback): infer _R5 + (...args: ArgsWithCallback): infer _R6 + (...args: ArgsWithCallback): infer _R7 + (...args: ArgsWithCallback): infer _R8 + } + ? NonNullable + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + (...args: ArgsWithCallback): infer _R5 + (...args: ArgsWithCallback): infer _R6 + (...args: ArgsWithCallback): infer _R7 + } + ? NonNullable + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + (...args: ArgsWithCallback): infer _R5 + (...args: ArgsWithCallback): infer _R6 + } + ? NonNullable + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + (...args: ArgsWithCallback): infer _R5 + } + ? NonNullable + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + } + ? NonNullable + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + } + ? NonNullable + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + } + ? NonNullable + : T extends { + (...args: ArgsWithCallback): infer _R1 + } + ? NonNullable + : never ``` Added in v1.0.0 @@ -151,132 +151,132 @@ export type Effectify = T extends { (...args: Args10): Effect.Effect> } : T extends { - (...args: ArgsWithCallback): infer _R1 - (...args: ArgsWithCallback): infer _R2 - (...args: ArgsWithCallback): infer _R3 - (...args: ArgsWithCallback): infer _R4 - (...args: ArgsWithCallback): infer _R5 - (...args: ArgsWithCallback): infer _R6 - (...args: ArgsWithCallback): infer _R7 - (...args: ArgsWithCallback): infer _R8 - (...args: ArgsWithCallback): infer _R9 - } - ? { - (...args: Args1): Effect.Effect> - (...args: Args2): Effect.Effect> - (...args: Args3): Effect.Effect> - (...args: Args4): Effect.Effect> - (...args: Args5): Effect.Effect> - (...args: Args6): Effect.Effect> - (...args: Args7): Effect.Effect> - (...args: Args8): Effect.Effect> - (...args: Args9): Effect.Effect> - } - : T extends { - (...args: ArgsWithCallback): infer _R1 - (...args: ArgsWithCallback): infer _R2 - (...args: ArgsWithCallback): infer _R3 - (...args: ArgsWithCallback): infer _R4 - (...args: ArgsWithCallback): infer _R5 - (...args: ArgsWithCallback): infer _R6 - (...args: ArgsWithCallback): infer _R7 - (...args: ArgsWithCallback): infer _R8 - } - ? { - (...args: Args1): Effect.Effect> - (...args: Args2): Effect.Effect> - (...args: Args3): Effect.Effect> - (...args: Args4): Effect.Effect> - (...args: Args5): Effect.Effect> - (...args: Args6): Effect.Effect> - (...args: Args7): Effect.Effect> - (...args: Args8): Effect.Effect> - } - : T extends { - (...args: ArgsWithCallback): infer _R1 - (...args: ArgsWithCallback): infer _R2 - (...args: ArgsWithCallback): infer _R3 - (...args: ArgsWithCallback): infer _R4 - (...args: ArgsWithCallback): infer _R5 - (...args: ArgsWithCallback): infer _R6 - (...args: ArgsWithCallback): infer _R7 - } - ? { - (...args: Args1): Effect.Effect> - (...args: Args2): Effect.Effect> - (...args: Args3): Effect.Effect> - (...args: Args4): Effect.Effect> - (...args: Args5): Effect.Effect> - (...args: Args6): Effect.Effect> - (...args: Args7): Effect.Effect> - } - : T extends { - (...args: ArgsWithCallback): infer _R1 - (...args: ArgsWithCallback): infer _R2 - (...args: ArgsWithCallback): infer _R3 - (...args: ArgsWithCallback): infer _R4 - (...args: ArgsWithCallback): infer _R5 - (...args: ArgsWithCallback): infer _R6 - } - ? { - (...args: Args1): Effect.Effect> - (...args: Args2): Effect.Effect> - (...args: Args3): Effect.Effect> - (...args: Args4): Effect.Effect> - (...args: Args5): Effect.Effect> - (...args: Args6): Effect.Effect> - } - : T extends { - (...args: ArgsWithCallback): infer _R1 - (...args: ArgsWithCallback): infer _R2 - (...args: ArgsWithCallback): infer _R3 - (...args: ArgsWithCallback): infer _R4 - (...args: ArgsWithCallback): infer _R5 - } - ? { - (...args: Args1): Effect.Effect> - (...args: Args2): Effect.Effect> - (...args: Args3): Effect.Effect> - (...args: Args4): Effect.Effect> - (...args: Args5): Effect.Effect> - } - : T extends { - (...args: ArgsWithCallback): infer _R1 - (...args: ArgsWithCallback): infer _R2 - (...args: ArgsWithCallback): infer _R3 - (...args: ArgsWithCallback): infer _R4 - } - ? { - (...args: Args1): Effect.Effect> - (...args: Args2): Effect.Effect> - (...args: Args3): Effect.Effect> - (...args: Args4): Effect.Effect> - } - : T extends { - (...args: ArgsWithCallback): infer _R1 - (...args: ArgsWithCallback): infer _R2 - (...args: ArgsWithCallback): infer _R3 - } - ? { - (...args: Args1): Effect.Effect> - (...args: Args2): Effect.Effect> - (...args: Args3): Effect.Effect> - } - : T extends { - (...args: ArgsWithCallback): infer _R1 - (...args: ArgsWithCallback): infer _R2 - } - ? { - (...args: Args1): Effect.Effect> - (...args: Args2): Effect.Effect> - } - : T extends { - (...args: ArgsWithCallback): infer _R1 - } - ? { - (...args: Args1): Effect.Effect> - } - : never + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + (...args: ArgsWithCallback): infer _R5 + (...args: ArgsWithCallback): infer _R6 + (...args: ArgsWithCallback): infer _R7 + (...args: ArgsWithCallback): infer _R8 + (...args: ArgsWithCallback): infer _R9 + } + ? { + (...args: Args1): Effect.Effect> + (...args: Args2): Effect.Effect> + (...args: Args3): Effect.Effect> + (...args: Args4): Effect.Effect> + (...args: Args5): Effect.Effect> + (...args: Args6): Effect.Effect> + (...args: Args7): Effect.Effect> + (...args: Args8): Effect.Effect> + (...args: Args9): Effect.Effect> + } + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + (...args: ArgsWithCallback): infer _R5 + (...args: ArgsWithCallback): infer _R6 + (...args: ArgsWithCallback): infer _R7 + (...args: ArgsWithCallback): infer _R8 + } + ? { + (...args: Args1): Effect.Effect> + (...args: Args2): Effect.Effect> + (...args: Args3): Effect.Effect> + (...args: Args4): Effect.Effect> + (...args: Args5): Effect.Effect> + (...args: Args6): Effect.Effect> + (...args: Args7): Effect.Effect> + (...args: Args8): Effect.Effect> + } + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + (...args: ArgsWithCallback): infer _R5 + (...args: ArgsWithCallback): infer _R6 + (...args: ArgsWithCallback): infer _R7 + } + ? { + (...args: Args1): Effect.Effect> + (...args: Args2): Effect.Effect> + (...args: Args3): Effect.Effect> + (...args: Args4): Effect.Effect> + (...args: Args5): Effect.Effect> + (...args: Args6): Effect.Effect> + (...args: Args7): Effect.Effect> + } + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + (...args: ArgsWithCallback): infer _R5 + (...args: ArgsWithCallback): infer _R6 + } + ? { + (...args: Args1): Effect.Effect> + (...args: Args2): Effect.Effect> + (...args: Args3): Effect.Effect> + (...args: Args4): Effect.Effect> + (...args: Args5): Effect.Effect> + (...args: Args6): Effect.Effect> + } + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + (...args: ArgsWithCallback): infer _R5 + } + ? { + (...args: Args1): Effect.Effect> + (...args: Args2): Effect.Effect> + (...args: Args3): Effect.Effect> + (...args: Args4): Effect.Effect> + (...args: Args5): Effect.Effect> + } + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + (...args: ArgsWithCallback): infer _R4 + } + ? { + (...args: Args1): Effect.Effect> + (...args: Args2): Effect.Effect> + (...args: Args3): Effect.Effect> + (...args: Args4): Effect.Effect> + } + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + (...args: ArgsWithCallback): infer _R3 + } + ? { + (...args: Args1): Effect.Effect> + (...args: Args2): Effect.Effect> + (...args: Args3): Effect.Effect> + } + : T extends { + (...args: ArgsWithCallback): infer _R1 + (...args: ArgsWithCallback): infer _R2 + } + ? { + (...args: Args1): Effect.Effect> + (...args: Args2): Effect.Effect> + } + : T extends { + (...args: ArgsWithCallback): infer _R1 + } + ? { + (...args: Args1): Effect.Effect> + } + : never ``` Added in v1.0.0 diff --git a/docs/platform/Error.ts.md b/docs/platform/Error.ts.md index 5ce19511..a192adf5 100644 --- a/docs/platform/Error.ts.md +++ b/docs/platform/Error.ts.md @@ -151,7 +151,7 @@ Added in v1.0.0 export interface Base extends Data.Case { readonly [PlatformErrorTypeId]: typeof PlatformErrorTypeId readonly _tag: string - readonly module: "Command" | "FileSystem" | "Path" | "KeyValueStore" | "Clipboard" | "Stream" + readonly module: "Clipboard" | "Command" | "FileSystem" | "KeyValueStore" | "Path" | "Stream" | "Terminal" readonly method: string readonly message: string } diff --git a/docs/platform/Http/FormData.ts.md b/docs/platform/Http/FormData.ts.md index 1121e7b4..d2db0057 100644 --- a/docs/platform/Http/FormData.ts.md +++ b/docs/platform/Http/FormData.ts.md @@ -12,32 +12,34 @@ Added in v1.0.0

Table of contents

-- [conversions](#conversions) - - [toRecord](#torecord) +- [constructors](#constructors) + - [formData](#formdata) + - [makeChannel](#makechannel) + - [makeConfig](#makeconfig) - [errors](#errors) - [FormDataError](#formdataerror) - [FormDataError (interface)](#formdataerror-interface) - [fiber refs](#fiber-refs) - [fieldMimeTypes](#fieldmimetypes) - [maxFieldSize](#maxfieldsize) - - [maxFields](#maxfields) - [maxFileSize](#maxfilesize) - - [maxFiles](#maxfiles) - [maxParts](#maxparts) - [withFieldMimeTypes](#withfieldmimetypes) - [withMaxFieldSize](#withmaxfieldsize) - - [withMaxFields](#withmaxfields) - [withMaxFileSize](#withmaxfilesize) - - [withMaxFiles](#withmaxfiles) - [withMaxParts](#withmaxparts) - [models](#models) - [Field (interface)](#field-interface) - [File (interface)](#file-interface) - [Part (type alias)](#part-type-alias) + - [PersistedFile (interface)](#persistedfile-interface) + - [PersistedFormData (interface)](#persistedformdata-interface) +- [refinements](#refinements) + - [isField](#isfield) - [schema](#schema) - [filesSchema](#filesschema) - [schemaJson](#schemajson) - - [schemaRecord](#schemarecord) + - [schemaPersisted](#schemapersisted) - [type ids](#type-ids) - [ErrorTypeId](#errortypeid) - [ErrorTypeId (type alias)](#errortypeid-type-alias) @@ -49,14 +51,40 @@ Added in v1.0.0 --- -# conversions +# constructors -## toRecord +## formData **Signature** ```ts -export declare const toRecord: (formData: FormData) => Record> +export declare const formData: ( + stream: Stream.Stream, + writeFile?: ((path: string, file: File) => Effect.Effect) | undefined +) => Effect.Effect +``` + +Added in v1.0.0 + +## makeChannel + +**Signature** + +```ts +export declare const makeChannel: ( + headers: Record, + bufferSize?: number +) => Channel.Channel, unknown, FormDataError | IE, Chunk.Chunk, unknown> +``` + +Added in v1.0.0 + +## makeConfig + +**Signature** + +```ts +export declare const makeConfig: (headers: Record) => Effect.Effect ``` Added in v1.0.0 @@ -81,7 +109,7 @@ Added in v1.0.0 export interface FormDataError extends Data.Case { readonly [ErrorTypeId]: ErrorTypeId readonly _tag: "FormDataError" - readonly reason: "FileTooLarge" | "FieldTooLarge" | "InternalError" | "Parse" + readonly reason: "FileTooLarge" | "FieldTooLarge" | "BodyTooLarge" | "TooManyParts" | "InternalError" | "Parse" readonly error: unknown } ``` @@ -110,16 +138,6 @@ export declare const maxFieldSize: FiberRef.FiberRef Added in v1.0.0 -## maxFields - -**Signature** - -```ts -export declare const maxFields: FiberRef.FiberRef> -``` - -Added in v1.0.0 - ## maxFileSize **Signature** @@ -130,16 +148,6 @@ export declare const maxFileSize: FiberRef.FiberRef> -``` - -Added in v1.0.0 - ## maxParts **Signature** @@ -176,19 +184,6 @@ export declare const withMaxFieldSize: { Added in v1.0.0 -## withMaxFields - -**Signature** - -```ts -export declare const withMaxFields: { - (count: Option.Option): (effect: Effect.Effect) => Effect.Effect - (effect: Effect.Effect, count: Option.Option): Effect.Effect -} -``` - -Added in v1.0.0 - ## withMaxFileSize **Signature** @@ -202,19 +197,6 @@ export declare const withMaxFileSize: { Added in v1.0.0 -## withMaxFiles - -**Signature** - -```ts -export declare const withMaxFiles: { - (count: Option.Option): (effect: Effect.Effect) => Effect.Effect - (effect: Effect.Effect, count: Option.Option): Effect.Effect -} -``` - -Added in v1.0.0 - ## withMaxParts **Signature** @@ -271,6 +253,46 @@ export type Part = Field | File Added in v1.0.0 +## PersistedFile (interface) + +**Signature** + +```ts +export interface PersistedFile extends Part.Proto { + readonly _tag: "PersistedFile" + readonly key: string + readonly name: string + readonly contentType: string + readonly path: string +} +``` + +Added in v1.0.0 + +## PersistedFormData (interface) + +**Signature** + +```ts +export interface PersistedFormData { + readonly [key: string]: ReadonlyArray | string +} +``` + +Added in v1.0.0 + +# refinements + +## isField + +**Signature** + +```ts +export declare const isField: (u: unknown) => u is Field +``` + +Added in v1.0.0 + # schema ## filesSchema @@ -278,7 +300,7 @@ Added in v1.0.0 **Signature** ```ts -export declare const filesSchema: Schema.Schema +export declare const filesSchema: Schema.Schema ``` Added in v1.0.0 @@ -291,21 +313,21 @@ Added in v1.0.0 export declare const schemaJson: ( schema: Schema.Schema ) => { - (field: string): (formData: FormData) => Effect.Effect - (formData: FormData, field: string): Effect.Effect + (field: string): (formData: PersistedFormData) => Effect.Effect + (formData: PersistedFormData, field: string): Effect.Effect } ``` Added in v1.0.0 -## schemaRecord +## schemaPersisted **Signature** ```ts -export declare const schemaRecord: >, A>( +export declare const schemaPersisted: ( schema: Schema.Schema -) => (formData: FormData) => Effect.Effect +) => (formData: PersistedFormData) => Effect.Effect ``` Added in v1.0.0 diff --git a/docs/platform/Http/ServerRequest.ts.md b/docs/platform/Http/ServerRequest.ts.md index 379c3ff4..fe4bea05 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) - - [formDataRecord](#formdatarecord) + - [persistedFormData](#persistedformdata) - [context](#context) - [ServerRequest](#serverrequest) - [fiber refs](#fiber-refs) @@ -34,15 +34,15 @@ Added in v1.0.0 # accessors -## formDataRecord +## persistedFormData **Signature** ```ts -export declare const formDataRecord: Effect.Effect< +export declare const persistedFormData: Effect.Effect< Scope.Scope | Path.Path | FileSystem.FileSystem | ServerRequest, FormData.FormDataError, - Record + unknown > ``` @@ -85,7 +85,11 @@ export interface ServerRequest extends IncomingMessage.IncomingMessage + readonly formData: Effect.Effect< + Scope.Scope | FileSystem.FileSystem | Path.Path, + FormData.FormDataError, + FormData.PersistedFormData + > readonly formDataStream: Stream.Stream readonly modify: (options: { @@ -129,7 +133,7 @@ Added in v1.0.0 **Signature** ```ts -export declare const schemaFormData: >, A>( +export declare const schemaFormData: ( schema: Schema.Schema ) => Effect.Effect< Scope.Scope | Path.Path | FileSystem.FileSystem | ServerRequest, diff --git a/docs/platform/Terminal.ts.md b/docs/platform/Terminal.ts.md index c92d3af1..49bc4665 100644 --- a/docs/platform/Terminal.ts.md +++ b/docs/platform/Terminal.ts.md @@ -95,6 +95,10 @@ user and display messages to a user. ```ts export interface Terminal { + /** + * The number of columns available on the platform's terminal interface. + */ + readonly columns: number /** * Reads a single input event from the default standard input. */ diff --git a/docs/platform/Worker.ts.md b/docs/platform/Worker.ts.md index 67fde13d..a8e99b6a 100644 --- a/docs/platform/Worker.ts.md +++ b/docs/platform/Worker.ts.md @@ -1,6 +1,6 @@ --- title: Worker.ts -nav_order: 31 +nav_order: 32 parent: "@effect/platform" --- diff --git a/docs/platform/WorkerError.ts.md b/docs/platform/WorkerError.ts.md index f307380e..b10e026d 100644 --- a/docs/platform/WorkerError.ts.md +++ b/docs/platform/WorkerError.ts.md @@ -1,6 +1,6 @@ --- title: WorkerError.ts -nav_order: 32 +nav_order: 33 parent: "@effect/platform" --- diff --git a/docs/platform/WorkerRunner.ts.md b/docs/platform/WorkerRunner.ts.md index ca3d85c8..64520075 100644 --- a/docs/platform/WorkerRunner.ts.md +++ b/docs/platform/WorkerRunner.ts.md @@ -1,6 +1,6 @@ --- title: WorkerRunner.ts -nav_order: 33 +nav_order: 34 parent: "@effect/platform" --- diff --git a/docs/platform/index.ts.md b/docs/platform/index.ts.md index fb1e9fe9..9c2556ff 100644 --- a/docs/platform/index.ts.md +++ b/docs/platform/index.ts.md @@ -23,6 +23,7 @@ Added in v1.0.0 - [From "./KeyValueStore.js"](#from-keyvaluestorejs) - [From "./Path.js"](#from-pathjs) - [From "./Runtime.js"](#from-runtimejs) + - [From "./Terminal.js"](#from-terminaljs) - [From "./Worker.js"](#from-workerjs) - [From "./WorkerError.js"](#from-workererrorjs) - [From "./WorkerRunner.js"](#from-workerrunnerjs) @@ -151,6 +152,18 @@ export * as Runtime from "./Runtime.js" Added in v1.0.0 +## From "./Terminal.js" + +Re-exports all named exports from the "./Terminal.js" module as `Terminal`. + +**Signature** + +```ts +export * as Terminal from "./Terminal.js" +``` + +Added in v1.0.0 + ## From "./Worker.js" Re-exports all named exports from the "./Worker.js" module as `Worker`. diff --git a/package.json b/package.json index d0cc58fc..66b2a64d 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "eslint-plugin-sort-destructure-keys": "^1.5.0", "glob": "^10.3.10", "madge": "^6.1.0", - "prettier": "^3.0.3", + "prettier": "^3.1.0", "typescript": "^5.2.2", "vite": "^4.5.0", "vitest": "^0.34.6" diff --git a/packages/platform-browser/src/Worker.ts b/packages/platform-browser/src/Worker.ts index 2caa171c..63c9cd5d 100644 --- a/packages/platform-browser/src/Worker.ts +++ b/packages/platform-browser/src/Worker.ts @@ -4,8 +4,8 @@ * Also includes exports from [`@effect/platform/Worker`](https://effect-ts.github.io/platform/platform/Worker.ts.html). */ import type * as Worker from "@effect/platform/Worker" -import type { Effect } from "effect" import type * as Context from "effect/Context" +import type * as Effect from "effect/Effect" import type * as Layer from "effect/Layer" import type * as Scope from "effect/Scope" import * as internal from "./internal/worker.js" diff --git a/packages/platform-browser/src/WorkerRunner.ts b/packages/platform-browser/src/WorkerRunner.ts index 9ac7e947..0f669708 100644 --- a/packages/platform-browser/src/WorkerRunner.ts +++ b/packages/platform-browser/src/WorkerRunner.ts @@ -5,7 +5,7 @@ */ import type { WorkerError } from "@effect/platform/WorkerError" import type * as Runner from "@effect/platform/WorkerRunner" -import type { Effect } from "effect" +import type * as Effect from "effect/Effect" import type * as Layer from "effect/Layer" import type * as Scope from "effect/Scope" import type * as Stream from "effect/Stream" diff --git a/packages/platform-bun/package.json b/packages/platform-bun/package.json index 1ee8d70d..0ec726a4 100644 --- a/packages/platform-bun/package.json +++ b/packages/platform-bun/package.json @@ -38,7 +38,7 @@ "effect": "2.0.0-next.54" }, "devDependencies": { - "@effect/schema": "^0.47.3", + "@effect/schema": "^0.47.6", "bun-types": "^1.0.11", "effect": "2.0.0-next.54" } diff --git a/packages/platform-bun/src/Worker.ts b/packages/platform-bun/src/Worker.ts index 889a3e08..a01b2602 100644 --- a/packages/platform-bun/src/Worker.ts +++ b/packages/platform-bun/src/Worker.ts @@ -4,8 +4,8 @@ * Also includes exports from [`@effect/platform/Worker`](https://effect-ts.github.io/platform/platform/Worker.ts.html). */ import type * as Worker from "@effect/platform/Worker" -import type { Effect } from "effect" import type * as Context from "effect/Context" +import type * as Effect from "effect/Effect" import type * as Layer from "effect/Layer" import type * as Scope from "effect/Scope" import * as internal from "./internal/worker.js" diff --git a/packages/platform-bun/src/WorkerRunner.ts b/packages/platform-bun/src/WorkerRunner.ts index 9ac7e947..0f669708 100644 --- a/packages/platform-bun/src/WorkerRunner.ts +++ b/packages/platform-bun/src/WorkerRunner.ts @@ -5,7 +5,7 @@ */ import type { WorkerError } from "@effect/platform/WorkerError" import type * as Runner from "@effect/platform/WorkerRunner" -import type { Effect } from "effect" +import type * as Effect from "effect/Effect" import type * as Layer from "effect/Layer" import type * as Scope from "effect/Scope" import type * as Stream from "effect/Stream" diff --git a/packages/platform-bun/src/internal/http/server.ts b/packages/platform-bun/src/internal/http/server.ts index c0d715c2..64875404 100644 --- a/packages/platform-bun/src/internal/http/server.ts +++ b/packages/platform-bun/src/internal/http/server.ts @@ -274,13 +274,13 @@ class ServerRequestImpl implements ServerRequest.ServerRequest { | Effect.Effect< Scope.Scope | FileSystem.FileSystem | Path.Path, FormData.FormDataError, - globalThis.FormData + FormData.PersistedFormData > | undefined get formData(): Effect.Effect< Scope.Scope | FileSystem.FileSystem | Path.Path, FormData.FormDataError, - globalThis.FormData + FormData.PersistedFormData > { if (this.formDataEffect) { return this.formDataEffect diff --git a/packages/platform-node/package.json b/packages/platform-node/package.json index 38d88777..33ad98f3 100644 --- a/packages/platform-node/package.json +++ b/packages/platform-node/package.json @@ -35,18 +35,17 @@ }, "dependencies": { "@effect/platform": "workspace:*", - "busboy": "^1.6.0", - "mime": "^3.0.0" + "mime": "^3.0.0", + "multipasta": "^0.1.11" }, "peerDependencies": { "effect": "2.0.0-next.54" }, "devDependencies": { - "@effect/schema": "^0.47.3", - "@types/busboy": "^1.5.3", + "@effect/schema": "^0.47.6", "@types/mime": "^3.0.4", "@types/node": "^20.9.0", - "@types/tar": "^6.1.8", + "@types/tar": "^6.1.9", "effect": "2.0.0-next.54", "tar": "^6.2.0" } diff --git a/packages/platform-node/src/Http/FormData.ts b/packages/platform-node/src/Http/FormData.ts index 3267edd2..b021db7b 100644 --- a/packages/platform-node/src/Http/FormData.ts +++ b/packages/platform-node/src/Http/FormData.ts @@ -34,5 +34,14 @@ export const stream: ( export const formData: ( source: Readable, headers: IncomingHttpHeaders -) => Effect.Effect = - internal.formData +) => Effect.Effect< + FileSystem.FileSystem | Path.Path | Scope.Scope, + FormData.FormDataError, + FormData.PersistedFormData +> = internal.formData + +/** + * @since 1.0.0 + * @category conversions + */ +export const fileToReadable: (file: FormData.File) => Readable = internal.fileToReadable diff --git a/packages/platform-node/src/Worker.ts b/packages/platform-node/src/Worker.ts index f6211351..6a282406 100644 --- a/packages/platform-node/src/Worker.ts +++ b/packages/platform-node/src/Worker.ts @@ -4,8 +4,8 @@ * Also includes exports from [`@effect/platform/Worker`](https://effect-ts.github.io/platform/platform/Worker.ts.html). */ import type * as Worker from "@effect/platform/Worker" -import type { Effect } from "effect" import type * as Context from "effect/Context" +import type * as Effect from "effect/Effect" import type * as Layer from "effect/Layer" import type * as Scope from "effect/Scope" import type * as WorkerThreads from "node:worker_threads" diff --git a/packages/platform-node/src/WorkerRunner.ts b/packages/platform-node/src/WorkerRunner.ts index 9ac7e947..0f669708 100644 --- a/packages/platform-node/src/WorkerRunner.ts +++ b/packages/platform-node/src/WorkerRunner.ts @@ -5,7 +5,7 @@ */ import type { WorkerError } from "@effect/platform/WorkerError" import type * as Runner from "@effect/platform/WorkerRunner" -import type { Effect } from "effect" +import type * as Effect from "effect/Effect" import type * as Layer from "effect/Layer" import type * as Scope from "effect/Scope" import type * as Stream from "effect/Stream" diff --git a/packages/platform-node/src/internal/http/formData.ts b/packages/platform-node/src/internal/http/formData.ts index 516e4eed..dac52c26 100644 --- a/packages/platform-node/src/internal/http/formData.ts +++ b/packages/platform-node/src/internal/http/formData.ts @@ -1,168 +1,109 @@ -import * as FileSystem from "@effect/platform/FileSystem" import * as FormData from "@effect/platform/Http/FormData" -import * as Path from "@effect/platform/Path" -import Busboy from "busboy" -import * as Chunk from "effect/Chunk" import * as Effect from "effect/Effect" -import * as FiberRef from "effect/FiberRef" import { pipe } from "effect/Function" -import * as Option from "effect/Option" import * as Stream from "effect/Stream" -import * as NodeFs from "node:fs" +import type { MultipartError, PartInfo } from "multipasta" +import { decodeField } from "multipasta" +import * as MP from "multipasta/node" +import * as NFS from "node:fs" import type { IncomingHttpHeaders } from "node:http" import type { Readable } from "node:stream" import * as NodeStreamP from "node:stream/promises" -import * as NodeStream from "../../Stream.js" +import * as NodeStream from "../stream.js" +/** @internal */ export const stream = ( source: Readable, headers: IncomingHttpHeaders ): Stream.Stream => pipe( - Effect.Do, - Effect.bind("maxParts", () => FiberRef.get(FormData.maxParts)), - Effect.bind("maxFields", () => FiberRef.get(FormData.maxFields)), - Effect.bind("maxFiles", () => FiberRef.get(FormData.maxFiles)), - Effect.bind("fieldMimeTypes", () => FiberRef.get(FormData.fieldMimeTypes)), - Effect.bind("maxFieldSize", () => FiberRef.get(FormData.maxFieldSize)), - Effect.bind("maxFileSize", () => FiberRef.get(FormData.maxFileSize)), - Effect.bind("busboy", ({ maxFieldSize, maxFields, maxFileSize, maxFiles, maxParts }) => - Effect.acquireRelease( - Effect.sync( - () => - Busboy({ - headers, - limits: { - parts: Option.getOrUndefined(Option.map(maxParts, Number)), - files: Option.getOrUndefined(Option.map(maxFiles, Number)), - fields: Option.getOrUndefined(Option.map(maxFields, Number)), - fieldSize: Number(maxFieldSize), - fileSize: Option.getOrUndefined(Option.map(maxFileSize, Number)) - } - }) - ), - (busboy) => - Effect.sync(() => { - busboy.removeAllListeners() - if (!busboy.closed) { - busboy.destroy() - } - }) - )), - Effect.map(({ busboy, fieldMimeTypes }) => - Stream.mapEffect( - Stream.async((emit) => { - busboy.on("field", (name, value, info) => { - if (info.valueTruncated) { - emit.fail(FormData.FormDataError("FieldTooLarge", new Error("maxFieldSize exceeded"))) - } else { - emit.single(new FieldImpl(name, info.mimeType, value)) - } - }) - - busboy.on("file", (name, stream, info) => { - stream.once("limit", () => { - emit.fail(FormData.FormDataError("FileTooLarge", new Error("maxFileSize exceeded"))) - }) - emit.single( - new FileImpl( - name, - info.filename, - info.mimeType, - NodeStream.fromReadable(() => stream, (error) => FormData.FormDataError("InternalError", error)), - stream - ) - ) - }) - - busboy.on("error", (_) => { - emit.fail(FormData.FormDataError("InternalError", _)) - }) - - busboy.on("finish", () => { - emit.end() - }) - - source.pipe(busboy) - }), - (part) => - part._tag === "File" && Chunk.some(fieldMimeTypes, (_) => part.contentType.includes(_)) ? - Effect.map( - NodeStream.toString(() => part.source, { - onFailure: (error) => FormData.FormDataError("InternalError", error) - }), - (content) => new FieldImpl(part.key, part.contentType, content) - ) - : Effect.succeed(part) - ) + FormData.makeConfig(headers as any), + Effect.map( + (config) => + NodeStream.fromReadable(() => { + const parser = MP.make(config) + source.pipe(parser) + return parser + }, (error) => convertError(error as any)) ), - Stream.unwrapScoped + Stream.unwrap, + Stream.map(convertPart) ) +/** @internal */ +export const formData = ( + source: Readable, + headers: IncomingHttpHeaders +) => + FormData.formData(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) + })) + +const convertPart = (part: MP.Part): FormData.Part => + part._tag === "Field" ? new FieldImpl(part.info, part.value) : new FileImpl(part) + class FieldImpl implements FormData.Field { readonly [FormData.TypeId]: FormData.TypeId readonly _tag = "Field" + readonly key: string + readonly contentType: string + readonly value: string + constructor( - readonly key: string, - readonly contentType: string, - readonly value: string + info: PartInfo, + value: Uint8Array ) { this[FormData.TypeId] = FormData.TypeId + this.key = info.name + this.contentType = info.contentType + this.value = decodeField(info, value) } } class FileImpl implements FormData.File { - readonly [FormData.TypeId]: FormData.TypeId readonly _tag = "File" + readonly [FormData.TypeId]: FormData.TypeId + readonly key: string + readonly name: string + readonly contentType: string + readonly content: Stream.Stream + constructor( - readonly key: string, - readonly name: string, - readonly contentType: string, - readonly content: Stream.Stream, - readonly source: Readable + readonly file: MP.FileStream ) { this[FormData.TypeId] = FormData.TypeId + this.key = file.info.name + this.name = file.filename + this.contentType = file.info.contentType + this.content = NodeStream.fromReadable(() => file, (error) => FormData.FormDataError("InternalError", error)) } } /** @internal */ -export const formData = ( - source: Readable, - headers: IncomingHttpHeaders -) => - Effect.flatMap( - Effect.all([ - Effect.mapError( - Effect.flatMap(FileSystem.FileSystem, (fs) => fs.makeTempDirectoryScoped()), - (error) => FormData.FormDataError("InternalError", error) - ), - Path.Path - ]), - ([dir, path_]) => - Stream.runFoldEffect( - stream(source, headers), - new globalThis.FormData(), - (formData, part) => { - if (part._tag === "Field") { - formData.append(part.key, part.value) - return Effect.succeed(formData) - } - const file = part as FileImpl - const path = path_.join(dir, file.name) - const blob = "Bun" in globalThis ? - (globalThis as any).Bun.file(path, { type: file.contentType }) - : new Blob([], { type: file.contentType }) - formData.append(part.key, blob, path) - return Effect.as( - Effect.tryPromise({ - try: (signal) => - NodeStreamP.pipeline(file.source, NodeFs.createWriteStream(path), { - signal - }), - catch: (error) => FormData.FormDataError("InternalError", error) - }), - formData - ) +export const fileToReadable = (file: FormData.File): Readable => (file as FileImpl).file + +function convertError(error: MultipartError): FormData.FormDataError { + switch (error._tag) { + case "ReachedLimit": { + switch (error.limit) { + case "MaxParts": { + return FormData.FormDataError("TooManyParts", error) } - ) - ) + case "MaxFieldSize": { + return FormData.FormDataError("FieldTooLarge", error) + } + case "MaxPartSize": { + return FormData.FormDataError("FileTooLarge", error) + } + case "MaxTotalSize": { + return FormData.FormDataError("BodyTooLarge", error) + } + } + } + default: { + return FormData.FormDataError("Parse", error) + } + } +} diff --git a/packages/platform-node/src/internal/http/server.ts b/packages/platform-node/src/internal/http/server.ts index f86cc3ae..168503bd 100644 --- a/packages/platform-node/src/internal/http/server.ts +++ b/packages/platform-node/src/internal/http/server.ts @@ -211,13 +211,13 @@ class ServerRequestImpl extends IncomingMessageImpl implemen | Effect.Effect< Scope.Scope | FileSystem.FileSystem | Path.Path, FormData.FormDataError, - globalThis.FormData + FormData.PersistedFormData > | undefined get formData(): Effect.Effect< Scope.Scope | FileSystem.FileSystem | Path.Path, FormData.FormDataError, - globalThis.FormData + FormData.PersistedFormData > { if (this.formDataEffect) { return this.formDataEffect diff --git a/packages/platform-node/test/HttpServer.test.ts b/packages/platform-node/test/HttpServer.test.ts index 218f527a..8975f977 100644 --- a/packages/platform-node/test/HttpServer.test.ts +++ b/packages/platform-node/test/HttpServer.test.ts @@ -9,7 +9,7 @@ import * as Layer from "effect/Layer" import * as Option from "effect/Option" import { createServer } from "http" import * as Buffer from "node:buffer" -import { describe, expect, it } from "vitest" +import { assert, describe, expect, it } from "vitest" const ServerLive = Http.server.layer(createServer, { port: 0 }) const EnvLive = Layer.mergeAll( @@ -80,10 +80,12 @@ describe("HttpServer", () => { Effect.gen(function*(_) { const request = yield* _(Http.request.ServerRequest) const formData = yield* _(request.formData) - const file = formData.get("file") as globalThis.File - expect(file.name.endsWith("/test.txt")).toEqual(true) - expect(file.type).toEqual("text/plain") - return yield* _(Http.response.json({ ok: formData.has("file") })) + const part = formData.file + assert(typeof part !== "string") + const file = part[0] + expect(file.path.endsWith("/test.txt")).toEqual(true) + expect(file.contentType).toEqual("text/plain") + return yield* _(Http.response.json({ ok: "file" in formData })) }).pipe(Effect.scoped) ), Http.server.serve(), diff --git a/packages/platform/package.json b/packages/platform/package.json index 06950157..914706a3 100644 --- a/packages/platform/package.json +++ b/packages/platform/package.json @@ -32,6 +32,7 @@ }, "dependencies": { "find-my-way": "^7.7.0", + "multipasta": "^0.1.11", "path-browserify": "^1.0.1" }, "peerDependencies": { @@ -39,7 +40,7 @@ "effect": "2.0.0-next.54" }, "devDependencies": { - "@effect/schema": "^0.47.3", + "@effect/schema": "^0.47.6", "@types/path-browserify": "^1.0.2", "effect": "2.0.0-next.54" } diff --git a/packages/platform/src/Http/FormData.ts b/packages/platform/src/Http/FormData.ts index f60a0f66..978a2805 100644 --- a/packages/platform/src/Http/FormData.ts +++ b/packages/platform/src/Http/FormData.ts @@ -3,14 +3,18 @@ */ import type * as ParseResult from "@effect/schema/ParseResult" import type * as Schema from "@effect/schema/Schema" +import type * as Channel from "effect/Channel" import type * as Chunk from "effect/Chunk" import type * as Data from "effect/Data" import type * as Effect from "effect/Effect" import type * as FiberRef from "effect/FiberRef" import type * as Option from "effect/Option" +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 type * as Path from "../Path.js" /** * @since 1.0.0 @@ -67,6 +71,26 @@ export interface File extends Part.Proto { readonly content: Stream.Stream } +/** + * @since 1.0.0 + * @category models + */ +export interface PersistedFile extends Part.Proto { + readonly _tag: "PersistedFile" + readonly key: string + readonly name: string + readonly contentType: string + readonly path: string +} + +/** + * @since 1.0.0 + * @category models + */ +export interface PersistedFormData { + readonly [key: string]: ReadonlyArray | string +} + /** * @since 1.0.0 * @category type ids @@ -86,7 +110,7 @@ export type ErrorTypeId = typeof ErrorTypeId export interface FormDataError extends Data.Case { readonly [ErrorTypeId]: ErrorTypeId readonly _tag: "FormDataError" - readonly reason: "FileTooLarge" | "FieldTooLarge" | "InternalError" | "Parse" + readonly reason: "FileTooLarge" | "FieldTooLarge" | "BodyTooLarge" | "TooManyParts" | "InternalError" | "Parse" readonly error: unknown } @@ -101,33 +125,24 @@ export const FormDataError: ( /** * @since 1.0.0 - * @category fiber refs - */ -export const maxParts: FiberRef.FiberRef> = internal.maxParts - -/** - * @since 1.0.0 - * @category fiber refs + * @category refinements */ -export const withMaxParts: { - (count: Option.Option): (effect: Effect.Effect) => Effect.Effect - (effect: Effect.Effect, count: Option.Option): Effect.Effect -} = internal.withMaxParts +export const isField: (u: unknown) => u is Field = internal.isField /** * @since 1.0.0 * @category fiber refs */ -export const maxFields: FiberRef.FiberRef> = internal.maxFields +export const maxParts: FiberRef.FiberRef> = internal.maxParts /** * @since 1.0.0 * @category fiber refs */ -export const withMaxFields: { +export const withMaxParts: { (count: Option.Option): (effect: Effect.Effect) => Effect.Effect (effect: Effect.Effect, count: Option.Option): Effect.Effect -} = internal.withMaxFields +} = internal.withMaxParts /** * @since 1.0.0 @@ -144,21 +159,6 @@ export const withMaxFieldSize: { (effect: Effect.Effect, size: FileSystem.SizeInput): Effect.Effect } = internal.withMaxFieldSize -/** - * @since 1.0.0 - * @category fiber refs - */ -export const maxFiles: FiberRef.FiberRef> = internal.maxFiles - -/** - * @since 1.0.0 - * @category fiber refs - */ -export const withMaxFiles: { - (count: Option.Option): (effect: Effect.Effect) => Effect.Effect - (effect: Effect.Effect, count: Option.Option): Effect.Effect -} = internal.withMaxFiles - /** * @since 1.0.0 * @category fiber refs @@ -189,17 +189,11 @@ export const withFieldMimeTypes: { (effect: Effect.Effect, mimeTypes: ReadonlyArray): Effect.Effect } = internal.withFieldMimeTypes -/** - * @since 1.0.0 - * @category conversions - */ -export const toRecord: (formData: FormData) => Record> = internal.toRecord - /** * @since 1.0.0 * @category schema */ -export const filesSchema: Schema.Schema, ReadonlyArray> = +export const filesSchema: Schema.Schema, ReadonlyArray> = internal.filesSchema /** @@ -209,14 +203,41 @@ export const filesSchema: Schema.Schema, Readonly export const schemaJson: ( schema: Schema.Schema ) => { - (field: string): (formData: FormData) => Effect.Effect - (formData: FormData, field: string): Effect.Effect + (field: string): (formData: PersistedFormData) => Effect.Effect + (formData: PersistedFormData, field: string): Effect.Effect } = internal.schemaJson /** * @since 1.0.0 * @category schema */ -export const schemaRecord: >>, A>( +export const schemaPersisted: ( schema: Schema.Schema -) => (formData: FormData) => Effect.Effect = internal.schemaRecord +) => (formData: PersistedFormData) => Effect.Effect = internal.schemaPersisted + +/** + * @since 1.0.0 + * @category constructors + */ +export const makeChannel: ( + headers: Record, + bufferSize?: number +) => Channel.Channel, unknown, FormDataError | IE, Chunk.Chunk, unknown> = + internal.makeChannel + +/** + * @since 1.0.0 + * @category constructors + */ +export const makeConfig: (headers: Record) => Effect.Effect = + internal.makeConfig + +/** + * @since 1.0.0 + * @category constructors + */ +export const formData: ( + stream: Stream.Stream, + writeFile?: (path: string, file: File) => Effect.Effect +) => Effect.Effect = + internal.formData diff --git a/packages/platform/src/Http/ServerRequest.ts b/packages/platform/src/Http/ServerRequest.ts index e9c50c94..33ebb993 100644 --- a/packages/platform/src/Http/ServerRequest.ts +++ b/packages/platform/src/Http/ServerRequest.ts @@ -46,7 +46,11 @@ export interface ServerRequest extends IncomingMessage.IncomingMessage + readonly formData: Effect.Effect< + Scope.Scope | FileSystem.FileSystem | Path.Path, + FormData.FormDataError, + FormData.PersistedFormData + > readonly formDataStream: Stream.Stream readonly modify: ( @@ -68,11 +72,11 @@ export const ServerRequest: Context.Tag = internal * @since 1.0.0 * @category accessors */ -export const formDataRecord: Effect.Effect< +export const persistedFormData: Effect.Effect< Scope.Scope | FileSystem.FileSystem | Path.Path | ServerRequest, FormData.FormDataError, - Record> -> = internal.formDataRecord + unknown +> = internal.persistedFormData /** * @since 1.0.0 @@ -102,7 +106,7 @@ export const schemaBodyUrlParams: >, A * @since 1.0.0 * @category schema */ -export const schemaFormData: >>, A>( +export const schemaFormData: ( schema: Schema.Schema ) => Effect.Effect< ServerRequest | Scope.Scope | FileSystem.FileSystem | Path.Path, diff --git a/packages/platform/src/WorkerRunner.ts b/packages/platform/src/WorkerRunner.ts index a5a47c06..ffd1522e 100644 --- a/packages/platform/src/WorkerRunner.ts +++ b/packages/platform/src/WorkerRunner.ts @@ -1,8 +1,8 @@ /** * @since 1.0.0 */ -import type { Effect } from "effect" import type * as Context from "effect/Context" +import type * as Effect from "effect/Effect" import type * as Fiber from "effect/Fiber" import type * as Queue from "effect/Queue" import type * as Scope from "effect/Scope" diff --git a/packages/platform/src/internal/http/formData.ts b/packages/platform/src/internal/http/formData.ts index 25a5fc4d..5b9ec888 100644 --- a/packages/platform/src/internal/http/formData.ts +++ b/packages/platform/src/internal/http/formData.ts @@ -1,16 +1,24 @@ import type * as ParseResult from "@effect/schema/ParseResult" import * as Schema from "@effect/schema/Schema" +import * as Cause from "effect/Cause" +import * as Channel from "effect/Channel" +import type * as AsyncInput from "effect/ChannelSingleProducerAsyncInput" import * as Chunk from "effect/Chunk" import * as Data from "effect/Data" import * as Effect from "effect/Effect" import * as FiberRef from "effect/FiberRef" -import { dual, pipe } from "effect/Function" +import { dual, flow, pipe } from "effect/Function" import { globalValue } from "effect/GlobalValue" import * as Option from "effect/Option" import * as Predicate from "effect/Predicate" -import * as ReadonlyArray from "effect/ReadonlyArray" +import * as Queue from "effect/Queue" +import type * as Scope from "effect/Scope" +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 * as Path from "../../Path.js" /** @internal */ export const TypeId: FormData.TypeId = Symbol.for("@effect/platform/Http/FormData") as FormData.TypeId @@ -29,6 +37,10 @@ export const FormDataError = (reason: FormData.FormDataError["reason"], error: u error }) +/** @internal */ +export const isField = (u: unknown): u is FormData.Field => + Predicate.hasProperty(u, TypeId) && Predicate.isTagged(u, "Field") + /** @internal */ export const maxParts: FiberRef.FiberRef> = globalValue( "@effect/platform/Http/FormData/maxParts", @@ -53,30 +65,6 @@ export const withMaxFieldSize = dual< (effect: Effect.Effect, size: FileSystem.SizeInput) => Effect.Effect >(2, (effect, size) => Effect.locally(effect, maxFieldSize, FileSystem.Size(size))) -/** @internal */ -export const maxFields: FiberRef.FiberRef> = globalValue( - "@effect/platform/Http/FormData/maxFields", - () => FiberRef.unsafeMake(Option.none()) -) - -/** @internal */ -export const withMaxFields = dual< - (count: Option.Option) => (effect: Effect.Effect) => Effect.Effect, - (effect: Effect.Effect, count: Option.Option) => Effect.Effect ->(2, (effect, count) => Effect.locally(effect, maxFields, count)) - -/** @internal */ -export const maxFiles: FiberRef.FiberRef> = globalValue( - "@effect/platform/Http/FormData/maxFiles", - () => FiberRef.unsafeMake(Option.none()) -) - -/** @internal */ -export const withMaxFiles = dual< - (count: Option.Option) => (effect: Effect.Effect) => Effect.Effect, - (effect: Effect.Effect, count: Option.Option) => Effect.Effect ->(2, (effect, count) => Effect.locally(effect, maxFiles, count)) - /** @internal */ export const maxFileSize: FiberRef.FiberRef> = globalValue( "@effect/platform/Http/FormData/maxFileSize", @@ -102,49 +90,32 @@ export const withFieldMimeTypes = dual< >(2, (effect, mimeTypes) => Effect.locally(effect, fieldMimeTypes, Chunk.fromIterable(mimeTypes))) /** @internal */ -export const toRecord = (formData: globalThis.FormData): Record | string> => - ReadonlyArray.reduce( - formData.entries(), - {} as Record | string>, - (acc, [key, value]) => { - if (Predicate.isString(value)) { - acc[key] = value - } else { - const existing = acc[key] - if (Array.isArray(existing)) { - existing.push(value) - } else { - acc[key] = [value] - } - } - return acc - } - ) -/** @internal */ -export const filesSchema: Schema.Schema, ReadonlyArray> = Schema.array( - pipe( - Schema.instanceOf(Blob), - Schema.filter( - (blob): blob is File => "name" in blob +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 ) - ) as any as Schema.Schema -) /** @internal */ -export const schemaRecord = >>, A>( +export const schemaPersisted = ( schema: Schema.Schema ) => { const parse = Schema.parse(schema) - return (formData: globalThis.FormData) => parse(toRecord(formData)) + return (formData: FormData.PersistedFormData) => parse(formData) } /** @internal */ export const schemaJson = (schema: Schema.Schema): { ( field: string - ): (formData: globalThis.FormData) => Effect.Effect + ): (formData: FormData.PersistedFormData) => Effect.Effect ( - formData: globalThis.FormData, + formData: FormData.PersistedFormData, field: string ): Effect.Effect } => { @@ -152,22 +123,309 @@ export const schemaJson = (schema: Schema.Schema): { return dual< ( field: string - ) => (formData: globalThis.FormData) => Effect.Effect, + ) => ( + formData: FormData.PersistedFormData + ) => Effect.Effect, ( - formData: globalThis.FormData, + formData: FormData.PersistedFormData, field: string ) => Effect.Effect >(2, (formData, field) => pipe( - Effect.succeed(formData.get(field)), + Effect.succeed(formData[field]), Effect.filterOrFail( - (field) => Predicate.isString(field), - () => FormDataError("Parse", `schemaJson: field was not a string`) + isField, + () => FormDataError("Parse", `schemaJson: was not a field`) ), Effect.tryMap({ - try: (field) => JSON.parse(field as string), + try: (field) => JSON.parse(field.value), catch: (error) => FormDataError("Parse", `schemaJson: field was not valid json: ${error}`) }), Effect.flatMap(parse) )) } + +/** @internal */ +export const makeConfig = ( + headers: Record +): Effect.Effect => + Effect.map( + Effect.all({ + maxParts: Effect.map(FiberRef.get(maxParts), Option.getOrUndefined), + maxFieldSize: Effect.map(FiberRef.get(maxFieldSize), Number), + maxPartSize: Effect.map(FiberRef.get(maxFileSize), flow(Option.map(Number), Option.getOrUndefined)), + maxTotalSize: Effect.map( + FiberRef.get(IncomingMessage.maxBodySize), + flow(Option.map(Number), Option.getOrUndefined) + ), + isFile: Effect.map(FiberRef.get(fieldMimeTypes), (mimeTypes) => { + if (mimeTypes.length === 0) { + return undefined + } + return (info: MP.PartInfo): boolean => + Chunk.some(mimeTypes, (_) => info.contentType.includes(_)) || MP.defaultIsFile(info) + }) + }), + (_) => ({ ..._, headers }) + ) + +/** @internal */ +export const makeChannel = ( + headers: Record, + bufferSize = 16 +): Channel.Channel< + never, + IE, + Chunk.Chunk, + unknown, + FormData.FormDataError | IE, + Chunk.Chunk, + unknown +> => + Channel.acquireUseRelease( + Effect.all([ + makeConfig(headers), + Queue.bounded | null>(bufferSize) + ]), + ([config, queue]) => makeFromQueue(config, queue), + ([, queue]) => Queue.shutdown(queue) + ) + +const makeFromQueue = ( + config: MP.BaseConfig, + queue: Queue.Queue | null> +): Channel.Channel< + never, + IE, + Chunk.Chunk, + unknown, + IE | FormData.FormDataError, + Chunk.Chunk, + unknown +> => + Channel.suspend(() => { + let error = Option.none>() + let partsBuffer: Array = [] + let partsFinished = false + + const input: AsyncInput.AsyncInputProducer, unknown> = { + awaitRead: () => Effect.unit, + emit(element) { + return Queue.offer(queue, element) + }, + error(cause) { + error = Option.some(cause) + return Queue.offer(queue, null) + }, + done(_value) { + return Queue.offer(queue, null) + } + } + + const parser = MP.make({ + ...config, + onField(info, value) { + partsBuffer.push(new FieldImpl(info.name, info.contentType, MP.decodeField(info, value))) + }, + onFile(info) { + let chunks: Array = [] + let finished = false + const take: Channel.Channel, void> = Channel + .suspend(() => { + if (finished) { + return Channel.unit + } else if (chunks.length === 0) { + return Channel.zipRight(pump, take) + } + const chunk = Chunk.unsafeFromArray(chunks) + chunks = [] + return Channel.zipRight( + Channel.write(chunk), + Channel.zipRight(pump, take) + ) + }) + partsBuffer.push(new FileImpl(info, take)) + return function(chunk) { + if (chunk === null) { + finished = true + } else { + chunks.push(chunk) + } + } + }, + onError(error_) { + error = Option.some(Cause.fail(convertError(error_))) + }, + onDone() { + partsFinished = true + } + }) + + const pump = Channel.flatMap( + Queue.take(queue), + (chunk) => + Channel.sync(() => { + if (chunk === null) { + parser.end() + } else { + Chunk.forEach(chunk, function(buf) { + parser.write(buf) + }) + } + }) + ) + + const takeParts = Channel.zipRight( + pump, + Channel.suspend(() => { + if (partsBuffer.length === 0) { + return Channel.unit + } + const parts = Chunk.unsafeFromArray(partsBuffer) + partsBuffer = [] + return Channel.write(parts) + }) + ) + + const partsChannel: Channel.Channel< + never, + unknown, + unknown, + unknown, + IE | FormData.FormDataError, + Chunk.Chunk, + void + > = Channel.suspend(() => { + if (error._tag === "Some") { + return Channel.failCause(error.value) + } else if (partsFinished) { + return Channel.unit + } + return Channel.zipRight(takeParts, partsChannel) + }) + + return Channel.embedInput(partsChannel, input) + }) + +function convertError(error: MP.MultipartError): FormData.FormDataError { + switch (error._tag) { + case "ReachedLimit": { + switch (error.limit) { + case "MaxParts": { + return FormDataError("TooManyParts", error) + } + case "MaxFieldSize": { + return FormDataError("FieldTooLarge", error) + } + case "MaxPartSize": { + return FormDataError("FileTooLarge", error) + } + case "MaxTotalSize": { + return FormDataError("BodyTooLarge", error) + } + } + } + default: { + return FormDataError("Parse", error) + } + } +} + +class FieldImpl implements FormData.Field { + readonly [TypeId]: FormData.TypeId + readonly _tag = "Field" + + constructor( + readonly key: string, + readonly contentType: string, + readonly value: string + ) { + this[TypeId] = TypeId + } +} + +class FileImpl implements FormData.File { + readonly _tag = "File" + readonly [TypeId]: FormData.TypeId + readonly key: string + readonly name: string + readonly contentType: string + readonly content: Stream.Stream + + constructor( + info: MP.PartInfo, + channel: Channel.Channel, void> + ) { + this[TypeId] = TypeId + this.key = info.name + this.name = info.filename ?? info.name + this.contentType = info.contentType + this.content = Stream.fromChannel(channel) + } +} + +const defaultWriteFile = (path: string, file: FormData.File) => + Effect.flatMap( + FileSystem.FileSystem, + (fs) => + Effect.mapError( + Stream.run(file.content, fs.sink(path)), + (error) => FormDataError("InternalError", error) + ) + ) + +/** @internal */ +export const formData = ( + stream: Stream.Stream, + writeFile = defaultWriteFile +): Effect.Effect => + pipe( + Effect.Do, + Effect.bind("fs", () => FileSystem.FileSystem), + Effect.bind("path", () => Path.Path), + Effect.bind("dir", ({ fs }) => fs.makeTempDirectoryScoped()), + Effect.flatMap(({ dir, path: path_ }) => + Stream.runFoldEffect( + stream, + Object.create(null) as Record | string>, + (formData, part) => { + if (part._tag === "Field") { + formData[part.key] = part.value + return Effect.succeed(formData) + } + const file = part + const path = path_.join(dir, path_.basename(file.name).slice(-128)) + if (!Array.isArray(formData[part.key])) { + formData[part.key] = [] + } + ;(formData[part.key] as Array).push( + new PersistedFileImpl( + file.key, + file.name, + file.contentType, + path + ) + ) + return Effect.as(writeFile(path, file), formData) + } + ) + ), + Effect.catchTags({ + SystemError: (err) => Effect.fail(FormDataError("InternalError", err)), + BadArgument: (err) => Effect.fail(FormDataError("InternalError", err)) + }) + ) + +class PersistedFileImpl implements FormData.PersistedFile { + readonly [TypeId]: FormData.TypeId + readonly _tag = "PersistedFile" + + constructor( + readonly key: string, + readonly name: string, + readonly contentType: string, + readonly path: string + ) { + this[TypeId] = TypeId + } +} diff --git a/packages/platform/src/internal/http/serverRequest.ts b/packages/platform/src/internal/http/serverRequest.ts index 57c9e222..b32b01a3 100644 --- a/packages/platform/src/internal/http/serverRequest.ts +++ b/packages/platform/src/internal/http/serverRequest.ts @@ -13,10 +13,7 @@ export const TypeId: ServerRequest.TypeId = Symbol.for("@effect/platform/Http/Se export const serverRequestTag = Context.Tag(TypeId) /** @internal */ -export const formDataRecord = Effect.map( - Effect.flatMap(serverRequestTag, (request) => request.formData), - FormData.toRecord -) +export const persistedFormData = Effect.flatMap(serverRequestTag, (request) => request.formData) /** @internal */ export const schemaHeaders = >, A>(schema: Schema.Schema) => { @@ -37,14 +34,11 @@ export const schemaBodyUrlParams = >, } /** @internal */ -export const schemaFormData = >>, A>( +export const schemaFormData = ( schema: Schema.Schema ) => { - const parse = FormData.schemaRecord(schema) - return Effect.flatMap( - Effect.flatMap(serverRequestTag, (request) => request.formData), - parse - ) + const parse = FormData.schemaPersisted(schema) + return Effect.flatMap(persistedFormData, parse) } /** @internal */ diff --git a/packages/platform/src/internal/worker.ts b/packages/platform/src/internal/worker.ts index ac278a44..c1c778b2 100644 --- a/packages/platform/src/internal/worker.ts +++ b/packages/platform/src/internal/worker.ts @@ -1,5 +1,6 @@ -import { Cause, Chunk } from "effect" +import * as Cause from "effect/Cause" import * as Channel from "effect/Channel" +import * as Chunk from "effect/Chunk" import * as Context from "effect/Context" import * as Deferred from "effect/Deferred" import * as Effect from "effect/Effect" diff --git a/packages/platform/test/Http/FormData.test.ts b/packages/platform/test/Http/FormData.test.ts new file mode 100644 index 00000000..d7d0dcd6 --- /dev/null +++ b/packages/platform/test/Http/FormData.test.ts @@ -0,0 +1,36 @@ +import * as FormData from "@effect/platform/Http/FormData" +import { Chunk, Effect, identity, Stream } from "effect" +import { assert, describe, test } from "vitest" + +describe("FormData", () => { + test("it parses", () => + Effect.gen(function*(_) { + const data = new globalThis.FormData() + data.append("foo", "bar") + data.append("test", "ing") + data.append("file", new globalThis.File(["foo"], "foo.txt", { type: "text/plain" })) + const response = new Response(data) + + const parts = yield* _( + Stream.fromReadableStream(() => response.body!, identity), + Stream.pipeThroughChannel(FormData.makeChannel(Object.fromEntries(response.headers))), + Stream.mapEffect((part) => + Effect.unified( + part._tag === "File" ? + Effect.zip( + Effect.succeed(part.name), + Stream.runLast(Stream.mkString(Stream.decodeText(part.content))).pipe(Effect.flatten) + ) : + Effect.succeed([part.key, part.value] as const) + ) + ), + Stream.runCollect + ) + + assert.deepStrictEqual(Chunk.toReadonlyArray(parts), [ + ["foo", "bar"], + ["test", "ing"], + ["foo.txt", "foo"] + ]) + }).pipe(Effect.runPromise)) +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2030895f..1fa10a67 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,10 +33,10 @@ importers: version: 2.26.2 '@effect/build-utils': specifier: ^0.4.0 - version: 0.4.0 + version: 0.4.1 '@effect/docgen': specifier: ^0.3.2 - version: 0.3.2(fast-check@3.13.2)(tsx@4.1.0)(typescript@5.2.2) + version: 0.3.2(fast-check@3.13.2)(tsx@4.1.1)(typescript@5.2.2) '@effect/eslint-plugin': specifier: ^0.1.2 version: 0.1.2 @@ -86,8 +86,8 @@ importers: specifier: ^6.1.0 version: 6.1.0(typescript@5.2.2) prettier: - specifier: ^3.0.3 - version: 3.0.3 + specifier: ^3.1.0 + version: 3.1.0 typescript: specifier: ^5.2.2 version: 5.2.2 @@ -103,13 +103,16 @@ importers: find-my-way: specifier: ^7.7.0 version: 7.7.0 + multipasta: + specifier: ^0.1.11 + version: 0.1.11 path-browserify: specifier: ^1.0.1 version: 1.0.1 devDependencies: '@effect/schema': - specifier: ^0.47.3 - version: 0.47.3(effect@2.0.0-next.54)(fast-check@3.13.2) + specifier: ^0.47.6 + version: 0.47.6(effect@2.0.0-next.54)(fast-check@3.13.2) '@types/path-browserify': specifier: ^1.0.2 version: 1.0.2 @@ -142,8 +145,8 @@ importers: version: link:../platform-node/dist devDependencies: '@effect/schema': - specifier: ^0.47.3 - version: 0.47.3(effect@2.0.0-next.54)(fast-check@3.13.2) + specifier: ^0.47.6 + version: 0.47.6(effect@2.0.0-next.54)(fast-check@3.13.2) bun-types: specifier: ^1.0.11 version: 1.0.11 @@ -157,19 +160,16 @@ importers: '@effect/platform': specifier: workspace:* version: link:../platform/dist - busboy: - specifier: ^1.6.0 - version: 1.6.0 mime: specifier: ^3.0.0 version: 3.0.0 + multipasta: + specifier: ^0.1.11 + version: 0.1.11 devDependencies: '@effect/schema': - specifier: ^0.47.3 - version: 0.47.3(effect@2.0.0-next.54)(fast-check@3.13.2) - '@types/busboy': - specifier: ^1.5.3 - version: 1.5.3 + specifier: ^0.47.6 + version: 0.47.6(effect@2.0.0-next.54)(fast-check@3.13.2) '@types/mime': specifier: ^3.0.4 version: 3.0.4 @@ -177,8 +177,8 @@ importers: specifier: ^20.9.0 version: 20.9.0 '@types/tar': - specifier: ^6.1.8 - version: 6.1.8 + specifier: ^6.1.9 + version: 6.1.9 effect: specifier: 2.0.0-next.54 version: 2.0.0-next.54 @@ -382,14 +382,6 @@ packages: js-tokens: 4.0.0 dev: true - /@babel/parser@7.23.0: - resolution: {integrity: sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.23.0 - dev: true - /@babel/parser@7.23.3: resolution: {integrity: sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==} engines: {node: '>=6.0.0'} @@ -464,15 +456,6 @@ packages: - supports-color dev: true - /@babel/types@7.23.0: - resolution: {integrity: sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-string-parser': 7.22.5 - '@babel/helper-validator-identifier': 7.22.20 - to-fast-properties: 2.0.0 - dev: true - /@babel/types@7.23.3: resolution: {integrity: sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==} engines: {node: '>=6.9.0'} @@ -706,13 +689,13 @@ packages: resolution: {integrity: sha512-rPwwm/RrFIolz6xHa8Kzpshuwpe+xu/XcEw9iUmRF2tnyIwxxaW7XoFKaQ+GfPju81cKpH4vJeq7/2IizKvyjg==} dev: true - /@effect/build-utils@0.4.0: - resolution: {integrity: sha512-1dL2qgzfGECCvMZ2BXhNjAdSDsKS9zpNJne8caxXRI8FtjFNPuj3HYOhYZVmaYfSvzwlC+p9nPBTRaiczoLe8w==} + /@effect/build-utils@0.4.1: + resolution: {integrity: sha512-p4Fm6jAnXEi1HaG6lRQN6uBWfFmMpoXetedMshuKO19EFk9OSAkcTl4Hb2wiE/Ex34xsmxg9gKlFpAZR6gc0ww==} engines: {node: '>=16.17.1'} hasBin: true dev: true - /@effect/docgen@0.3.2(fast-check@3.13.2)(tsx@4.1.0)(typescript@5.2.2): + /@effect/docgen@0.3.2(fast-check@3.13.2)(tsx@4.1.1)(typescript@5.2.2): resolution: {integrity: sha512-2Iq0p+Qp+hzHi9BGKZ7dbZ35c+RZwDQJN5YHk85AyHUM8bdj+9ZS2zMthCFJqciW8i4NcugjQ+QYYmWGqBeFYA==} engines: {node: '>=16.17.1'} hasBin: true @@ -720,17 +703,17 @@ packages: tsx: ^4.1.0 typescript: ^5.2.2 dependencies: - '@effect/platform-node': 0.29.0(@effect/schema@0.47.3)(effect@2.0.0-next.54) + '@effect/platform-node': 0.29.2(@effect/schema@0.47.3)(effect@2.0.0-next.54) '@effect/schema': 0.47.3(effect@2.0.0-next.54)(fast-check@3.13.2) chalk: 5.3.0 doctrine: 3.0.0 effect: 2.0.0-next.54 glob: 10.3.10 markdown-toc: github.com/effect-ts/markdown-toc/4bfeb0f140105440ea0d12df2fa23199cc3ec1d5 - prettier: 3.0.3 + prettier: 3.1.0 ts-morph: 20.0.0 tsconfck: 3.0.0(typescript@5.2.2) - tsx: 4.1.0 + tsx: 4.1.1 typescript: 5.2.2 transitivePeerDependencies: - fast-check @@ -748,12 +731,12 @@ packages: resolution: {integrity: sha512-e8vfKbjnbYiyneBincEFS0tzXluopGK77OkVFbPRtUbNDS5tJfb+jiwOQEiqASDsadcZmd+9J9+Q6v/z7GuN2g==} dev: true - /@effect/platform-node@0.29.0(@effect/schema@0.47.3)(effect@2.0.0-next.54): - resolution: {integrity: sha512-P5kA72swAni4D38eI5eovGIve0fhMLpWJf/IfMwMbfhGbNRo8cZ+/Q0KYbmMn9ilp2fxdEw6MEX9hu/cqYw56A==} + /@effect/platform-node@0.29.2(@effect/schema@0.47.3)(effect@2.0.0-next.54): + resolution: {integrity: sha512-1dhxBSa3NQhuTDRPFQKnpMPJ8hsWGPxIIYQbsJ/6oOADah9cZWWjqiUzTuvOnKHEMQcIc9glIjb0OvAhjSimBw==} peerDependencies: effect: 2.0.0-next.54 dependencies: - '@effect/platform': 0.28.0(@effect/schema@0.47.3)(effect@2.0.0-next.54) + '@effect/platform': 0.28.2(@effect/schema@0.47.3)(effect@2.0.0-next.54) busboy: 1.6.0 effect: 2.0.0-next.54 mime: 3.0.0 @@ -761,10 +744,10 @@ packages: - '@effect/schema' dev: true - /@effect/platform@0.28.0(@effect/schema@0.47.3)(effect@2.0.0-next.54): - resolution: {integrity: sha512-zPtn6PMFGJ+hHtCVC9vKSEo4ZSNtMoQhHaC2k+6TNpCT+xaiKTYmwuEImuHf1qdWSQ0py2m5ivo/8kR5ryoeeg==} + /@effect/platform@0.28.2(@effect/schema@0.47.3)(effect@2.0.0-next.54): + resolution: {integrity: sha512-LLqA9dqPljn57Or2n2m74ogZbt4DNxwI6JRcl2ve7pxEMgX2xrVmGZ7VeWFYJbjFqIqaZ6rSOkbjSSGILyQkPQ==} peerDependencies: - '@effect/schema': ^0.47.1 + '@effect/schema': ^0.47.3 effect: 2.0.0-next.54 dependencies: '@effect/schema': 0.47.3(effect@2.0.0-next.54)(fast-check@3.13.2) @@ -783,6 +766,16 @@ packages: fast-check: 3.13.2 dev: true + /@effect/schema@0.47.6(effect@2.0.0-next.54)(fast-check@3.13.2): + resolution: {integrity: sha512-+pH1/ByUnWa71tirb3kYMdCfWXjmYvU/2wZOfG3vmF8XQWcR3WnIlI1R5DvVYxtLp872a1kDiYX618h6ta6w9g==} + peerDependencies: + effect: 2.0.0-next.54 + fast-check: ^3.13.2 + dependencies: + effect: 2.0.0-next.54 + fast-check: 3.13.2 + dev: true + /@esbuild/android-arm64@0.18.20: resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} engines: {node: '>=12'} @@ -1170,12 +1163,6 @@ packages: path-browserify: 1.0.1 dev: true - /@types/busboy@1.5.3: - resolution: {integrity: sha512-YMBLFN/xBD8bnqywIlGyYqsNFXu6bsiY7h3Ae0kO17qEuTjsqeyYMRPSUDacIKIquws2Y6KjmxAyNx8xB3xQbw==} - dependencies: - '@types/node': 20.9.0 - dev: true - /@types/chai-subset@1.3.4: resolution: {integrity: sha512-CCWNXrJYSUIojZ1149ksLl3AN9cmZ5djf+yUoVVV+NuYrtydItQVlL2ZDqyC6M6O9LWRnVf8yYDxbXHO2TfQZg==} dependencies: @@ -1244,8 +1231,8 @@ packages: undici-types: 5.26.5 dev: true - /@types/normalize-package-data@2.4.3: - resolution: {integrity: sha512-ehPtgRgaULsFG8x0NeYJvmyH1hmlfsNLujHe9dQEia/7MAJYdzMSi19JtchUHjmBA6XC/75dK55mzZH+RyieSg==} + /@types/normalize-package-data@2.4.4: + resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} dev: true /@types/path-browserify@1.0.2: @@ -1260,8 +1247,8 @@ packages: resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} dev: true - /@types/tar@6.1.8: - resolution: {integrity: sha512-p8Rc0vas9n+7cv0JiSdUMHiL42gOflrj3v0RMSbe8x5lauEygCEFpS0B/Rw9ZI62qrxlsvZqTggIEIas2gBQrA==} + /@types/tar@6.1.9: + resolution: {integrity: sha512-T3+O+OQd9cdGmOXuKQY9+B0ceZHRlGVPQ7M5QZqkaPyP/vWnxPXM2aCegq8GP/n1n9dBfq2EBUqCSKKjQAVOPA==} dependencies: '@types/node': 20.9.0 minipass: 4.2.8 @@ -1902,6 +1889,7 @@ packages: engines: {node: '>=10.16.0'} dependencies: streamsearch: 1.1.0 + dev: true /cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} @@ -4310,6 +4298,10 @@ packages: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} dev: true + /multipasta@0.1.11: + resolution: {integrity: sha512-uigM3fzPPIS2pduTmN+D8V/fLN3lx3GtqUR9ikX9f0OwK0DL3sIdy1h5+oEMUz46WjFFGFnY4NebdgJLo++T+w==} + dev: false + /nanoid@3.3.6: resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -4344,14 +4336,14 @@ packages: resolution: {integrity: sha512-8Q1hXew6ETzqKRAs3jjLioSxNfT1cx74ooiF8RlAONwVMcfq+UdzLC2eB5qcPldUxaE5w3ytLkrmV1TGddhZTA==} engines: {node: '>=6.0'} dependencies: - '@babel/parser': 7.23.0 + '@babel/parser': 7.23.3 dev: true /node-source-walk@5.0.2: resolution: {integrity: sha512-Y4jr/8SRS5hzEdZ7SGuvZGwfORvNsSsNRwDXx5WisiqzsVfeftDvRgfeqWNgZvWSJbgubTRVRYBzK6UO+ErqjA==} engines: {node: '>=12'} dependencies: - '@babel/parser': 7.23.0 + '@babel/parser': 7.23.3 dev: true /normalize-package-data@2.5.0: @@ -4717,8 +4709,8 @@ packages: hasBin: true dev: true - /prettier@3.0.3: - resolution: {integrity: sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==} + /prettier@3.1.0: + resolution: {integrity: sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==} engines: {node: '>=14'} hasBin: true dev: true @@ -4819,7 +4811,7 @@ packages: resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} engines: {node: '>=8'} dependencies: - '@types/normalize-package-data': 2.4.3 + '@types/normalize-package-data': 2.4.4 normalize-package-data: 2.5.0 parse-json: 5.2.0 type-fest: 0.6.0 @@ -5239,6 +5231,7 @@ packages: /streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} + dev: true /string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} @@ -5573,8 +5566,8 @@ packages: typescript: 5.2.2 dev: true - /tsx@4.1.0: - resolution: {integrity: sha512-u4l17Yd63Wsk2fzNn1wZCmcS9kwJ/2ysl7wuoVggv2hd3NjLA5JQPpyJMXoWSXOwOvoQUzNcu/sf/35HEsnXsg==} + /tsx@4.1.1: + resolution: {integrity: sha512-zyPn5BFMB0TB5kMLbYPNx4x/oL/oSlaecdKCv6WeJ0TeSEfx8RTJWjuB5TZ2dSewktgfBsBO/HNA9mrMWqLXMA==} engines: {node: '>=18.0.0'} hasBin: true dependencies: