Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@effect/platform

Package Overview
Dependencies
Maintainers
3
Versions
399
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@effect/platform - npm Package Compare versions

Comparing version 0.28.4 to 0.29.0

47

dist/cjs/Http/FormData.js

@@ -6,3 +6,3 @@ "use strict";

});
exports.withMaxParts = exports.withMaxFiles = exports.withMaxFileSize = exports.withMaxFields = exports.withMaxFieldSize = exports.withFieldMimeTypes = exports.toRecord = exports.schemaRecord = exports.schemaJson = exports.maxParts = exports.maxFiles = exports.maxFileSize = exports.maxFields = exports.maxFieldSize = exports.filesSchema = exports.fieldMimeTypes = exports.TypeId = exports.FormDataError = exports.ErrorTypeId = void 0;
exports.withMaxParts = exports.withMaxFileSize = exports.withMaxFieldSize = exports.withFieldMimeTypes = exports.schemaPersisted = exports.schemaJson = exports.maxParts = exports.maxFileSize = exports.maxFieldSize = exports.makeConfig = exports.makeChannel = exports.isField = exports.formData = exports.filesSchema = exports.fieldMimeTypes = exports.TypeId = exports.FormDataError = exports.ErrorTypeId = void 0;
var internal = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("../internal/http/formData.js"));

@@ -51,5 +51,5 @@ function _getRequireWildcardCache(e) {

* @since 1.0.0
* @category fiber refs
* @category refinements
*/
const maxParts = exports.maxParts = internal.maxParts;
const isField = exports.isField = internal.isField;
/**

@@ -59,3 +59,3 @@ * @since 1.0.0

*/
const withMaxParts = exports.withMaxParts = internal.withMaxParts;
const maxParts = exports.maxParts = internal.maxParts;
/**

@@ -65,3 +65,3 @@ * @since 1.0.0

*/
const maxFields = exports.maxFields = internal.maxFields;
const withMaxParts = exports.withMaxParts = internal.withMaxParts;
/**

@@ -71,7 +71,2 @@ * @since 1.0.0

*/
const withMaxFields = exports.withMaxFields = internal.withMaxFields;
/**
* @since 1.0.0
* @category fiber refs
*/
const maxFieldSize = exports.maxFieldSize = internal.maxFieldSize;

@@ -87,12 +82,2 @@ /**

*/
const maxFiles = exports.maxFiles = internal.maxFiles;
/**
* @since 1.0.0
* @category fiber refs
*/
const withMaxFiles = exports.withMaxFiles = internal.withMaxFiles;
/**
* @since 1.0.0
* @category fiber refs
*/
const maxFileSize = exports.maxFileSize = internal.maxFileSize;

@@ -116,7 +101,2 @@ /**

* @since 1.0.0
* @category conversions
*/
const toRecord = exports.toRecord = internal.toRecord;
/**
* @since 1.0.0
* @category schema

@@ -134,3 +114,18 @@ */

*/
const schemaRecord = exports.schemaRecord = internal.schemaRecord;
const schemaPersisted = exports.schemaPersisted = internal.schemaPersisted;
/**
* @since 1.0.0
* @category constructors
*/
const makeChannel = exports.makeChannel = internal.makeChannel;
/**
* @since 1.0.0
* @category constructors
*/
const makeConfig = exports.makeConfig = internal.makeConfig;
/**
* @since 1.0.0
* @category constructors
*/
const formData = exports.formData = internal.formData;
//# sourceMappingURL=FormData.js.map

@@ -6,3 +6,3 @@ "use strict";

});
exports.formDataRecord = exports.TypeId = exports.ServerRequest = void 0;
exports.TypeId = exports.ServerRequest = void 0;
Object.defineProperty(exports, "maxBodySize", {

@@ -14,3 +14,3 @@ enumerable: true,

});
exports.schemaHeaders = exports.schemaFormDataJson = exports.schemaFormData = exports.schemaBodyUrlParams = exports.schemaBodyJson = void 0;
exports.schemaHeaders = exports.schemaFormDataJson = exports.schemaFormData = exports.schemaBodyUrlParams = exports.schemaBodyJson = exports.persistedFormData = void 0;
var internal = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("../internal/http/serverRequest.js"));

@@ -57,3 +57,3 @@ var _IncomingMessage = /*#__PURE__*/require("./IncomingMessage.js");

*/
const formDataRecord = exports.formDataRecord = internal.formDataRecord;
const persistedFormData = exports.persistedFormData = internal.persistedFormData;
/**

@@ -60,0 +60,0 @@ * @since 1.0.0

@@ -6,4 +6,6 @@ "use strict";

});
exports.withMaxParts = exports.withMaxFiles = exports.withMaxFileSize = exports.withMaxFields = exports.withMaxFieldSize = exports.withFieldMimeTypes = exports.toRecord = exports.schemaRecord = exports.schemaJson = exports.maxParts = exports.maxFiles = exports.maxFileSize = exports.maxFields = exports.maxFieldSize = exports.filesSchema = exports.fieldMimeTypes = exports.TypeId = exports.FormDataError = exports.ErrorTypeId = void 0;
exports.withMaxParts = exports.withMaxFileSize = exports.withMaxFieldSize = exports.withFieldMimeTypes = exports.schemaPersisted = exports.schemaJson = exports.maxParts = exports.maxFileSize = exports.maxFieldSize = exports.makeConfig = exports.makeChannel = exports.isField = exports.formData = exports.filesSchema = exports.fieldMimeTypes = exports.TypeId = exports.FormDataError = exports.ErrorTypeId = void 0;
var Schema = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("@effect/schema/Schema"));
var Cause = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("effect/Cause"));
var Channel = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("effect/Channel"));
var Chunk = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("effect/Chunk"));

@@ -17,4 +19,8 @@ var Data = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("effect/Data"));

var Predicate = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("effect/Predicate"));
var ReadonlyArray = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("effect/ReadonlyArray"));
var Queue = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("effect/Queue"));
var Stream = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("effect/Stream"));
var MP = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("multipasta"));
var FileSystem = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("../../FileSystem.js"));
var IncomingMessage = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("../../Http/IncomingMessage.js"));
var Path = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("../../Path.js"));
function _getRequireWildcardCache(e) {

@@ -58,2 +64,5 @@ if ("function" != typeof WeakMap) return null;

exports.FormDataError = FormDataError;
const isField = u => Predicate.hasProperty(u, TypeId) && Predicate.isTagged(u, "Field");
/** @internal */
exports.isField = isField;
const maxParts = exports.maxParts = /*#__PURE__*/(0, _GlobalValue.globalValue)("@effect/platform/Http/FormData/maxParts", () => FiberRef.unsafeMake(Option.none()));

@@ -67,10 +76,2 @@ /** @internal */

/** @internal */
const maxFields = exports.maxFields = /*#__PURE__*/(0, _GlobalValue.globalValue)("@effect/platform/Http/FormData/maxFields", () => FiberRef.unsafeMake(Option.none()));
/** @internal */
const withMaxFields = exports.withMaxFields = /*#__PURE__*/(0, _Function.dual)(2, (effect, count) => Effect.locally(effect, maxFields, count));
/** @internal */
const maxFiles = exports.maxFiles = /*#__PURE__*/(0, _GlobalValue.globalValue)("@effect/platform/Http/FormData/maxFiles", () => FiberRef.unsafeMake(Option.none()));
/** @internal */
const withMaxFiles = exports.withMaxFiles = /*#__PURE__*/(0, _Function.dual)(2, (effect, count) => Effect.locally(effect, maxFiles, count));
/** @internal */
const maxFileSize = exports.maxFileSize = /*#__PURE__*/(0, _GlobalValue.globalValue)("@effect/platform/Http/FormData/maxFileSize", () => FiberRef.unsafeMake(Option.none()));

@@ -84,33 +85,213 @@ /** @internal */

/** @internal */
const toRecord = formData => ReadonlyArray.reduce(formData.entries(), {}, (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;
});
const filesSchema = exports.filesSchema = /*#__PURE__*/Schema.array( /*#__PURE__*/(0, _Function.pipe)(Schema.object, /*#__PURE__*/Schema.filter(file => TypeId in file && "_tag" in file && file._tag === "PersistedFile")));
/** @internal */
exports.toRecord = toRecord;
const filesSchema = exports.filesSchema = /*#__PURE__*/Schema.array( /*#__PURE__*/(0, _Function.pipe)( /*#__PURE__*/Schema.instanceOf(Blob), /*#__PURE__*/Schema.filter(blob => "name" in blob)));
/** @internal */
const schemaRecord = schema => {
const schemaPersisted = schema => {
const parse = Schema.parse(schema);
return formData => parse(toRecord(formData));
return formData => parse(formData);
};
/** @internal */
exports.schemaRecord = schemaRecord;
exports.schemaPersisted = schemaPersisted;
const schemaJson = schema => {
const parse = Schema.parse(schema);
return (0, _Function.dual)(2, (formData, field) => (0, _Function.pipe)(Effect.succeed(formData.get(field)), Effect.filterOrFail(field => Predicate.isString(field), () => FormDataError("Parse", `schemaJson: field was not a string`)), Effect.tryMap({
try: field => JSON.parse(field),
return (0, _Function.dual)(2, (formData, field) => (0, _Function.pipe)(Effect.succeed(formData[field]), Effect.filterOrFail(isField, () => FormDataError("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}`)
}), Effect.flatMap(parse)));
};
/** @internal */
exports.schemaJson = schemaJson;
const makeConfig = headers => 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), (0, _Function.flow)(Option.map(Number), Option.getOrUndefined)),
maxTotalSize: Effect.map(FiberRef.get(IncomingMessage.maxBodySize), (0, _Function.flow)(Option.map(Number), Option.getOrUndefined)),
isFile: Effect.map(FiberRef.get(fieldMimeTypes), mimeTypes => {
if (mimeTypes.length === 0) {
return undefined;
}
return info => Chunk.some(mimeTypes, _ => info.contentType.includes(_)) || MP.defaultIsFile(info);
})
}), _ => ({
..._,
headers
}));
/** @internal */
exports.makeConfig = makeConfig;
const makeChannel = (headers, bufferSize = 16) => Channel.acquireUseRelease(Effect.all([makeConfig(headers), Queue.bounded(bufferSize)]), ([config, queue]) => makeFromQueue(config, queue), ([, queue]) => Queue.shutdown(queue));
exports.makeChannel = makeChannel;
const makeFromQueue = (config, queue) => Channel.suspend(() => {
let error = Option.none();
let partsBuffer = [];
let partsFinished = false;
const input = {
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 = [];
let finished = false;
const take = 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.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) {
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 {
key;
contentType;
value;
[TypeId];
_tag = "Field";
constructor(key, contentType, value) {
this.key = key;
this.contentType = contentType;
this.value = value;
this[TypeId] = TypeId;
}
}
class FileImpl {
_tag = "File";
[TypeId];
key;
name;
contentType;
content;
constructor(info, channel) {
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, file) => Effect.flatMap(FileSystem.FileSystem, fs => Effect.mapError(Stream.run(file.content, fs.sink(path)), error => FormDataError("InternalError", error)));
/** @internal */
const formData = (stream, writeFile = defaultWriteFile) => (0, _Function.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), (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].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))
}));
exports.formData = formData;
class PersistedFileImpl {
key;
name;
contentType;
path;
[TypeId];
_tag = "PersistedFile";
constructor(key, name, contentType, path) {
this.key = key;
this.name = name;
this.contentType = contentType;
this.path = path;
this[TypeId] = TypeId;
}
}
//# sourceMappingURL=formData.js.map

@@ -6,3 +6,3 @@ "use strict";

});
exports.serverRequestTag = exports.schemaHeaders = exports.schemaFormDataJson = exports.schemaFormData = exports.schemaBodyUrlParams = exports.schemaBodyJson = exports.formDataRecord = exports.TypeId = void 0;
exports.serverRequestTag = exports.schemaHeaders = exports.schemaFormDataJson = exports.schemaFormData = exports.schemaBodyUrlParams = exports.schemaBodyJson = exports.persistedFormData = exports.TypeId = void 0;
var Context = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("effect/Context"));

@@ -43,3 +43,3 @@ var Effect = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("effect/Effect"));

/** @internal */
const formDataRecord = exports.formDataRecord = /*#__PURE__*/Effect.map( /*#__PURE__*/Effect.flatMap(serverRequestTag, request => request.formData), FormData.toRecord);
const persistedFormData = exports.persistedFormData = /*#__PURE__*/Effect.flatMap(serverRequestTag, request => request.formData);
/** @internal */

@@ -65,4 +65,4 @@ const schemaHeaders = schema => {

const schemaFormData = 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);
};

@@ -69,0 +69,0 @@ /** @internal */

@@ -7,4 +7,5 @@ "use strict";

exports.makePoolLayer = exports.makePool = exports.makeManager = exports.layerManager = exports.defaultQueue = exports.WorkerManagerTypeId = exports.WorkerManager = exports.PlatformWorkerTypeId = exports.PlatformWorker = void 0;
var _effect = /*#__PURE__*/require("effect");
var Cause = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("effect/Cause"));
var Channel = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("effect/Channel"));
var Chunk = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("effect/Chunk"));
var Context = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("effect/Context"));

@@ -82,3 +83,3 @@ var Deferred = /*#__PURE__*/_interopRequireWildcard( /*#__PURE__*/require("effect/Deferred"));

const backing = yield* _(platform.spawn(spawn(id)));
yield* _(Effect.addFinalizer(() => Effect.zipRight(Effect.forEach(requestMap.values(), ([queue]) => Queue.offer(queue, Exit.failCause(_effect.Cause.empty)), {
yield* _(Effect.addFinalizer(() => Effect.zipRight(Effect.forEach(requestMap.values(), ([queue]) => Queue.offer(queue, Exit.failCause(Cause.empty)), {
discard: true

@@ -106,3 +107,3 @@ }), Effect.sync(() => requestMap.clear()))));

{
return response.length === 2 ? Queue.offer(queue[0], Exit.failCause(_effect.Cause.empty)) : Effect.zipRight(Queue.offer(queue[0], Exit.succeed(response[2])), Queue.offer(queue[0], Exit.failCause(_effect.Cause.empty)));
return response.length === 2 ? Queue.offer(queue[0], Exit.failCause(Cause.empty)) : Effect.zipRight(Queue.offer(queue[0], Exit.succeed(response[2])), Queue.offer(queue[0], Exit.failCause(Cause.empty)));
}

@@ -129,4 +130,4 @@ // error / defect

const loop = Channel.flatMap(Queue.take(queue), Exit.match({
onFailure: cause => _effect.Cause.isEmpty(cause) ? Channel.unit : Channel.failCause(cause),
onSuccess: value => Channel.flatMap(Channel.write(_effect.Chunk.of(value)), () => loop)
onFailure: cause => Cause.isEmpty(cause) ? Channel.unit : Channel.failCause(cause),
onSuccess: value => Channel.flatMap(Channel.write(Chunk.of(value)), () => loop)
}));

@@ -133,0 +134,0 @@ return Stream.fromChannel(loop);

@@ -6,2 +6,3 @@ /**

import type * as Schema from "@effect/schema/Schema";
import type * as Channel from "effect/Channel";
import type * as Chunk from "effect/Chunk";

@@ -12,4 +13,7 @@ import type * as Data from "effect/Data";

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 type * as Path from "../Path.js";
/**

@@ -66,2 +70,20 @@ * @since 1.0.0

* @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<PersistedFile> | string;
}
/**
* @since 1.0.0
* @category type ids

@@ -82,3 +104,3 @@ */

readonly _tag: "FormDataError";
readonly reason: "FileTooLarge" | "FieldTooLarge" | "InternalError" | "Parse";
readonly reason: "FileTooLarge" | "FieldTooLarge" | "BodyTooLarge" | "TooManyParts" | "InternalError" | "Parse";
readonly error: unknown;

@@ -93,5 +115,5 @@ }

* @since 1.0.0
* @category fiber refs
* @category refinements
*/
export declare const maxParts: FiberRef.FiberRef<Option.Option<number>>;
export declare const isField: (u: unknown) => u is Field;
/**

@@ -101,6 +123,3 @@ * @since 1.0.0

*/
export declare const withMaxParts: {
(count: Option.Option<number>): <R, E, A>(effect: Effect.Effect<R, E, A>) => Effect.Effect<R, E, A>;
<R, E, A>(effect: Effect.Effect<R, E, A>, count: Option.Option<number>): Effect.Effect<R, E, A>;
};
export declare const maxParts: FiberRef.FiberRef<Option.Option<number>>;
/**

@@ -110,8 +129,3 @@ * @since 1.0.0

*/
export declare const maxFields: FiberRef.FiberRef<Option.Option<number>>;
/**
* @since 1.0.0
* @category fiber refs
*/
export declare const withMaxFields: {
export declare const withMaxParts: {
(count: Option.Option<number>): <R, E, A>(effect: Effect.Effect<R, E, A>) => Effect.Effect<R, E, A>;

@@ -137,15 +151,2 @@ <R, E, A>(effect: Effect.Effect<R, E, A>, count: Option.Option<number>): Effect.Effect<R, E, A>;

*/
export declare const maxFiles: FiberRef.FiberRef<Option.Option<number>>;
/**
* @since 1.0.0
* @category fiber refs
*/
export declare const withMaxFiles: {
(count: Option.Option<number>): <R, E, A>(effect: Effect.Effect<R, E, A>) => Effect.Effect<R, E, A>;
<R, E, A>(effect: Effect.Effect<R, E, A>, count: Option.Option<number>): Effect.Effect<R, E, A>;
};
/**
* @since 1.0.0
* @category fiber refs
*/
export declare const maxFileSize: FiberRef.FiberRef<Option.Option<FileSystem.Size>>;

@@ -175,10 +176,5 @@ /**

* @since 1.0.0
* @category conversions
*/
export declare const toRecord: (formData: FormData) => Record<string, string | Array<globalThis.File>>;
/**
* @since 1.0.0
* @category schema
*/
export declare const filesSchema: Schema.Schema<ReadonlyArray<globalThis.File>, ReadonlyArray<globalThis.File>>;
export declare const filesSchema: Schema.Schema<ReadonlyArray<PersistedFile>, ReadonlyArray<PersistedFile>>;
/**

@@ -189,4 +185,4 @@ * @since 1.0.0

export declare const schemaJson: <I, A>(schema: Schema.Schema<I, A>) => {
(field: string): (formData: FormData) => Effect.Effect<never, FormDataError | ParseResult.ParseError, A>;
(formData: FormData, field: string): Effect.Effect<never, FormDataError | ParseResult.ParseError, A>;
(field: string): (formData: PersistedFormData) => Effect.Effect<never, FormDataError | ParseResult.ParseError, A>;
(formData: PersistedFormData, field: string): Effect.Effect<never, FormDataError | ParseResult.ParseError, A>;
};

@@ -197,3 +193,18 @@ /**

*/
export declare const schemaRecord: <I extends Readonly<Record<string, string | ReadonlyArray<globalThis.File>>>, A>(schema: Schema.Schema<I, A>) => (formData: FormData) => Effect.Effect<never, ParseResult.ParseError, A>;
export declare const schemaPersisted: <I extends PersistedFormData, A>(schema: Schema.Schema<I, A>) => (formData: PersistedFormData) => Effect.Effect<never, ParseResult.ParseError, A>;
/**
* @since 1.0.0
* @category constructors
*/
export declare const makeChannel: <IE>(headers: Record<string, string>, bufferSize?: number) => Channel.Channel<never, IE, Chunk.Chunk<Uint8Array>, unknown, FormDataError | IE, Chunk.Chunk<Part>, unknown>;
/**
* @since 1.0.0
* @category constructors
*/
export declare const makeConfig: (headers: Record<string, string>) => Effect.Effect<never, never, Multipasta.BaseConfig>;
/**
* @since 1.0.0
* @category constructors
*/
export declare const formData: (stream: Stream.Stream<never, FormDataError, Part>, writeFile?: (path: string, file: File) => Effect.Effect<FileSystem.FileSystem, FormDataError, void>) => Effect.Effect<FileSystem.FileSystem | Path.Path | Scope.Scope, FormDataError, PersistedFormData>;
//# sourceMappingURL=FormData.d.ts.map

@@ -42,3 +42,3 @@ /**

readonly method: Method;
readonly formData: Effect.Effect<Scope.Scope | FileSystem.FileSystem | Path.Path, FormData.FormDataError, FormData>;
readonly formData: Effect.Effect<Scope.Scope | FileSystem.FileSystem | Path.Path, FormData.FormDataError, FormData.PersistedFormData>;
readonly formDataStream: Stream.Stream<never, FormData.FormDataError, FormData.Part>;

@@ -60,3 +60,3 @@ readonly modify: (options: {

*/
export declare const formDataRecord: Effect.Effect<Scope.Scope | FileSystem.FileSystem | Path.Path | ServerRequest, FormData.FormDataError, Record<string, string | Array<File>>>;
export declare const persistedFormData: Effect.Effect<Scope.Scope | FileSystem.FileSystem | Path.Path | ServerRequest, FormData.FormDataError, unknown>;
/**

@@ -81,3 +81,3 @@ * @since 1.0.0

*/
export declare const schemaFormData: <I extends Readonly<Record<string, string | ReadonlyArray<File>>>, A>(schema: Schema.Schema<I, A>) => Effect.Effect<ServerRequest | Scope.Scope | FileSystem.FileSystem | Path.Path, FormData.FormDataError | ParseResult.ParseError, A>;
export declare const schemaFormData: <I extends FormData.PersistedFormData, A>(schema: Schema.Schema<I, A>) => Effect.Effect<ServerRequest | Scope.Scope | FileSystem.FileSystem | Path.Path, FormData.FormDataError | ParseResult.ParseError, A>;
/**

@@ -84,0 +84,0 @@ * @since 1.0.0

/**
* @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";

@@ -7,0 +7,0 @@ import type * as Queue from "effect/Queue";

@@ -19,5 +19,5 @@ import * as internal from "../internal/http/formData.js";

* @since 1.0.0
* @category fiber refs
* @category refinements
*/
export const maxParts = internal.maxParts;
export const isField = internal.isField;
/**

@@ -27,3 +27,3 @@ * @since 1.0.0

*/
export const withMaxParts = internal.withMaxParts;
export const maxParts = internal.maxParts;
/**

@@ -33,3 +33,3 @@ * @since 1.0.0

*/
export const maxFields = internal.maxFields;
export const withMaxParts = internal.withMaxParts;
/**

@@ -39,7 +39,2 @@ * @since 1.0.0

*/
export const withMaxFields = internal.withMaxFields;
/**
* @since 1.0.0
* @category fiber refs
*/
export const maxFieldSize = internal.maxFieldSize;

@@ -55,12 +50,2 @@ /**

*/
export const maxFiles = internal.maxFiles;
/**
* @since 1.0.0
* @category fiber refs
*/
export const withMaxFiles = internal.withMaxFiles;
/**
* @since 1.0.0
* @category fiber refs
*/
export const maxFileSize = internal.maxFileSize;

@@ -84,7 +69,2 @@ /**

* @since 1.0.0
* @category conversions
*/
export const toRecord = internal.toRecord;
/**
* @since 1.0.0
* @category schema

@@ -102,3 +82,18 @@ */

*/
export const schemaRecord = internal.schemaRecord;
export const schemaPersisted = internal.schemaPersisted;
/**
* @since 1.0.0
* @category constructors
*/
export const makeChannel = internal.makeChannel;
/**
* @since 1.0.0
* @category constructors
*/
export const makeConfig = internal.makeConfig;
/**
* @since 1.0.0
* @category constructors
*/
export const formData = internal.formData;
//# sourceMappingURL=FormData.js.map

@@ -22,3 +22,3 @@ import * as internal from "../internal/http/serverRequest.js";

*/
export const formDataRecord = internal.formDataRecord;
export const persistedFormData = internal.persistedFormData;
/**

@@ -25,0 +25,0 @@ * @since 1.0.0

import * as Schema from "@effect/schema/Schema";
import * as Cause from "effect/Cause";
import * as Channel from "effect/Channel";
import * as Chunk from "effect/Chunk";

@@ -6,8 +8,12 @@ import * as Data from "effect/Data";

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 * as Stream from "effect/Stream";
import * as MP from "multipasta";
import * as FileSystem from "../../FileSystem.js";
import * as IncomingMessage from "../../Http/IncomingMessage.js";
import * as Path from "../../Path.js";
/** @internal */

@@ -25,2 +31,4 @@ export const TypeId = /*#__PURE__*/Symbol.for("@effect/platform/Http/FormData");

/** @internal */
export const isField = u => Predicate.hasProperty(u, TypeId) && Predicate.isTagged(u, "Field");
/** @internal */
export const maxParts = /*#__PURE__*/globalValue("@effect/platform/Http/FormData/maxParts", () => FiberRef.unsafeMake(Option.none()));

@@ -34,10 +42,2 @@ /** @internal */

/** @internal */
export const maxFields = /*#__PURE__*/globalValue("@effect/platform/Http/FormData/maxFields", () => FiberRef.unsafeMake(Option.none()));
/** @internal */
export const withMaxFields = /*#__PURE__*/dual(2, (effect, count) => Effect.locally(effect, maxFields, count));
/** @internal */
export const maxFiles = /*#__PURE__*/globalValue("@effect/platform/Http/FormData/maxFiles", () => FiberRef.unsafeMake(Option.none()));
/** @internal */
export const withMaxFiles = /*#__PURE__*/dual(2, (effect, count) => Effect.locally(effect, maxFiles, count));
/** @internal */
export const maxFileSize = /*#__PURE__*/globalValue("@effect/platform/Http/FormData/maxFileSize", () => FiberRef.unsafeMake(Option.none()));

@@ -51,21 +51,7 @@ /** @internal */

/** @internal */
export const toRecord = formData => ReadonlyArray.reduce(formData.entries(), {}, (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;
});
export const filesSchema = /*#__PURE__*/Schema.array( /*#__PURE__*/pipe(Schema.object, /*#__PURE__*/Schema.filter(file => TypeId in file && "_tag" in file && file._tag === "PersistedFile")));
/** @internal */
export const filesSchema = /*#__PURE__*/Schema.array( /*#__PURE__*/pipe( /*#__PURE__*/Schema.instanceOf(Blob), /*#__PURE__*/Schema.filter(blob => "name" in blob)));
/** @internal */
export const schemaRecord = schema => {
export const schemaPersisted = schema => {
const parse = Schema.parse(schema);
return formData => parse(toRecord(formData));
return formData => parse(formData);
};

@@ -75,7 +61,199 @@ /** @internal */

const parse = Schema.parse(schema);
return dual(2, (formData, field) => pipe(Effect.succeed(formData.get(field)), Effect.filterOrFail(field => Predicate.isString(field), () => FormDataError("Parse", `schemaJson: field was not a string`)), Effect.tryMap({
try: field => JSON.parse(field),
return dual(2, (formData, field) => pipe(Effect.succeed(formData[field]), Effect.filterOrFail(isField, () => FormDataError("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}`)
}), Effect.flatMap(parse)));
};
/** @internal */
export const makeConfig = headers => 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 => Chunk.some(mimeTypes, _ => info.contentType.includes(_)) || MP.defaultIsFile(info);
})
}), _ => ({
..._,
headers
}));
/** @internal */
export const makeChannel = (headers, bufferSize = 16) => Channel.acquireUseRelease(Effect.all([makeConfig(headers), Queue.bounded(bufferSize)]), ([config, queue]) => makeFromQueue(config, queue), ([, queue]) => Queue.shutdown(queue));
const makeFromQueue = (config, queue) => Channel.suspend(() => {
let error = Option.none();
let partsBuffer = [];
let partsFinished = false;
const input = {
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 = [];
let finished = false;
const take = 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.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) {
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 {
key;
contentType;
value;
[TypeId];
_tag = "Field";
constructor(key, contentType, value) {
this.key = key;
this.contentType = contentType;
this.value = value;
this[TypeId] = TypeId;
}
}
class FileImpl {
_tag = "File";
[TypeId];
key;
name;
contentType;
content;
constructor(info, channel) {
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, file) => Effect.flatMap(FileSystem.FileSystem, fs => Effect.mapError(Stream.run(file.content, fs.sink(path)), error => FormDataError("InternalError", error)));
/** @internal */
export const formData = (stream, writeFile = defaultWriteFile) => 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), (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].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 {
key;
name;
contentType;
path;
[TypeId];
_tag = "PersistedFile";
constructor(key, name, contentType, path) {
this.key = key;
this.name = name;
this.contentType = contentType;
this.path = path;
this[TypeId] = TypeId;
}
}
//# sourceMappingURL=formData.js.map

@@ -11,3 +11,3 @@ import * as Context from "effect/Context";

/** @internal */
export const formDataRecord = /*#__PURE__*/Effect.map( /*#__PURE__*/Effect.flatMap(serverRequestTag, request => request.formData), FormData.toRecord);
export const persistedFormData = /*#__PURE__*/Effect.flatMap(serverRequestTag, request => request.formData);
/** @internal */

@@ -30,4 +30,4 @@ export const schemaHeaders = schema => {

export const schemaFormData = 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);
};

@@ -34,0 +34,0 @@ /** @internal */

@@ -1,3 +0,4 @@

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";

@@ -4,0 +5,0 @@ import * as Deferred from "effect/Deferred";

{
"name": "@effect/platform",
"version": "0.28.4",
"version": "0.29.0",
"description": "Unified interfaces for common platform-specific services",

@@ -14,2 +14,3 @@ "license": "MIT",

"find-my-way": "^7.7.0",
"multipasta": "^0.1.11",
"path-browserify": "^1.0.1"

@@ -16,0 +17,0 @@ },

@@ -6,2 +6,3 @@ /**

import type * as Schema from "@effect/schema/Schema"
import type * as Channel from "effect/Channel"
import type * as Chunk from "effect/Chunk"

@@ -12,5 +13,8 @@ import type * as Data from "effect/Data"

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"

@@ -74,2 +78,22 @@ /**

* @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<PersistedFile> | string
}
/**
* @since 1.0.0
* @category type ids

@@ -92,3 +116,3 @@ */

readonly _tag: "FormDataError"
readonly reason: "FileTooLarge" | "FieldTooLarge" | "InternalError" | "Parse"
readonly reason: "FileTooLarge" | "FieldTooLarge" | "BodyTooLarge" | "TooManyParts" | "InternalError" | "Parse"
readonly error: unknown

@@ -108,5 +132,5 @@ }

* @since 1.0.0
* @category fiber refs
* @category refinements
*/
export const maxParts: FiberRef.FiberRef<Option.Option<number>> = internal.maxParts
export const isField: (u: unknown) => u is Field = internal.isField

@@ -117,6 +141,3 @@ /**

*/
export const withMaxParts: {
(count: Option.Option<number>): <R, E, A>(effect: Effect.Effect<R, E, A>) => Effect.Effect<R, E, A>
<R, E, A>(effect: Effect.Effect<R, E, A>, count: Option.Option<number>): Effect.Effect<R, E, A>
} = internal.withMaxParts
export const maxParts: FiberRef.FiberRef<Option.Option<number>> = internal.maxParts

@@ -127,12 +148,6 @@ /**

*/
export const maxFields: FiberRef.FiberRef<Option.Option<number>> = internal.maxFields
/**
* @since 1.0.0
* @category fiber refs
*/
export const withMaxFields: {
export const withMaxParts: {
(count: Option.Option<number>): <R, E, A>(effect: Effect.Effect<R, E, A>) => Effect.Effect<R, E, A>
<R, E, A>(effect: Effect.Effect<R, E, A>, count: Option.Option<number>): Effect.Effect<R, E, A>
} = internal.withMaxFields
} = internal.withMaxParts

@@ -158,17 +173,2 @@ /**

*/
export const maxFiles: FiberRef.FiberRef<Option.Option<number>> = internal.maxFiles
/**
* @since 1.0.0
* @category fiber refs
*/
export const withMaxFiles: {
(count: Option.Option<number>): <R, E, A>(effect: Effect.Effect<R, E, A>) => Effect.Effect<R, E, A>
<R, E, A>(effect: Effect.Effect<R, E, A>, count: Option.Option<number>): Effect.Effect<R, E, A>
} = internal.withMaxFiles
/**
* @since 1.0.0
* @category fiber refs
*/
export const maxFileSize: FiberRef.FiberRef<Option.Option<FileSystem.Size>> = internal.maxFileSize

@@ -202,11 +202,5 @@

* @since 1.0.0
* @category conversions
*/
export const toRecord: (formData: FormData) => Record<string, string | Array<globalThis.File>> = internal.toRecord
/**
* @since 1.0.0
* @category schema
*/
export const filesSchema: Schema.Schema<ReadonlyArray<globalThis.File>, ReadonlyArray<globalThis.File>> =
export const filesSchema: Schema.Schema<ReadonlyArray<PersistedFile>, ReadonlyArray<PersistedFile>> =
internal.filesSchema

@@ -221,4 +215,4 @@

) => {
(field: string): (formData: FormData) => Effect.Effect<never, FormDataError | ParseResult.ParseError, A>
(formData: FormData, field: string): Effect.Effect<never, FormDataError | ParseResult.ParseError, A>
(field: string): (formData: PersistedFormData) => Effect.Effect<never, FormDataError | ParseResult.ParseError, A>
(formData: PersistedFormData, field: string): Effect.Effect<never, FormDataError | ParseResult.ParseError, A>
} = internal.schemaJson

@@ -230,4 +224,31 @@

*/
export const schemaRecord: <I extends Readonly<Record<string, string | ReadonlyArray<globalThis.File>>>, A>(
export const schemaPersisted: <I extends PersistedFormData, A>(
schema: Schema.Schema<I, A>
) => (formData: FormData) => Effect.Effect<never, ParseResult.ParseError, A> = internal.schemaRecord
) => (formData: PersistedFormData) => Effect.Effect<never, ParseResult.ParseError, A> = internal.schemaPersisted
/**
* @since 1.0.0
* @category constructors
*/
export const makeChannel: <IE>(
headers: Record<string, string>,
bufferSize?: number
) => Channel.Channel<never, IE, Chunk.Chunk<Uint8Array>, unknown, FormDataError | IE, Chunk.Chunk<Part>, unknown> =
internal.makeChannel
/**
* @since 1.0.0
* @category constructors
*/
export const makeConfig: (headers: Record<string, string>) => Effect.Effect<never, never, Multipasta.BaseConfig> =
internal.makeConfig
/**
* @since 1.0.0
* @category constructors
*/
export const formData: (
stream: Stream.Stream<never, FormDataError, Part>,
writeFile?: (path: string, file: File) => Effect.Effect<FileSystem.FileSystem, FormDataError, void>
) => Effect.Effect<FileSystem.FileSystem | Path.Path | Scope.Scope, FormDataError, PersistedFormData> =
internal.formData

@@ -49,3 +49,7 @@ /**

readonly formData: Effect.Effect<Scope.Scope | FileSystem.FileSystem | Path.Path, FormData.FormDataError, FormData>
readonly formData: Effect.Effect<
Scope.Scope | FileSystem.FileSystem | Path.Path,
FormData.FormDataError,
FormData.PersistedFormData
>
readonly formDataStream: Stream.Stream<never, FormData.FormDataError, FormData.Part>

@@ -72,7 +76,7 @@

*/
export const formDataRecord: Effect.Effect<
export const persistedFormData: Effect.Effect<
Scope.Scope | FileSystem.FileSystem | Path.Path | ServerRequest,
FormData.FormDataError,
Record<string, string | Array<File>>
> = internal.formDataRecord
unknown
> = internal.persistedFormData

@@ -107,3 +111,3 @@ /**

*/
export const schemaFormData: <I extends Readonly<Record<string, string | ReadonlyArray<File>>>, A>(
export const schemaFormData: <I extends FormData.PersistedFormData, A>(
schema: Schema.Schema<I, A>

@@ -110,0 +114,0 @@ ) => Effect.Effect<

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"

@@ -7,9 +10,14 @@ import * as Data from "effect/Data"

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"

@@ -34,2 +42,6 @@ /** @internal */

/** @internal */
export const isField = (u: unknown): u is FormData.Field =>
Predicate.hasProperty(u, TypeId) && Predicate.isTagged(u, "Field")
/** @internal */
export const maxParts: FiberRef.FiberRef<Option.Option<number>> = globalValue(

@@ -59,26 +71,2 @@ "@effect/platform/Http/FormData/maxParts",

/** @internal */
export const maxFields: FiberRef.FiberRef<Option.Option<number>> = globalValue(
"@effect/platform/Http/FormData/maxFields",
() => FiberRef.unsafeMake(Option.none<number>())
)
/** @internal */
export const withMaxFields = dual<
(count: Option.Option<number>) => <R, E, A>(effect: Effect.Effect<R, E, A>) => Effect.Effect<R, E, A>,
<R, E, A>(effect: Effect.Effect<R, E, A>, count: Option.Option<number>) => Effect.Effect<R, E, A>
>(2, (effect, count) => Effect.locally(effect, maxFields, count))
/** @internal */
export const maxFiles: FiberRef.FiberRef<Option.Option<number>> = globalValue(
"@effect/platform/Http/FormData/maxFiles",
() => FiberRef.unsafeMake(Option.none<number>())
)
/** @internal */
export const withMaxFiles = dual<
(count: Option.Option<number>) => <R, E, A>(effect: Effect.Effect<R, E, A>) => Effect.Effect<R, E, A>,
<R, E, A>(effect: Effect.Effect<R, E, A>, count: Option.Option<number>) => Effect.Effect<R, E, A>
>(2, (effect, count) => Effect.locally(effect, maxFiles, count))
/** @internal */
export const maxFileSize: FiberRef.FiberRef<Option.Option<FileSystem.Size>> = globalValue(

@@ -108,36 +96,19 @@ "@effect/platform/Http/FormData/maxFileSize",

/** @internal */
export const toRecord = (formData: globalThis.FormData): Record<string, Array<globalThis.File> | string> =>
ReadonlyArray.reduce(
formData.entries(),
{} as Record<string, Array<globalThis.File> | 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<File>, ReadonlyArray<File>> = Schema.array(
pipe(
Schema.instanceOf(Blob),
Schema.filter(
(blob): blob is File => "name" in blob
export const filesSchema: Schema.Schema<ReadonlyArray<FormData.PersistedFile>, ReadonlyArray<FormData.PersistedFile>> =
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<FormData.PersistedFile, FormData.PersistedFile>
)
) as any as Schema.Schema<File, File>
)
/** @internal */
export const schemaRecord = <I extends Readonly<Record<string, string | ReadonlyArray<globalThis.File>>>, A>(
export const schemaPersisted = <I extends FormData.PersistedFormData, A>(
schema: Schema.Schema<I, A>
) => {
const parse = Schema.parse(schema)
return (formData: globalThis.FormData) => parse(toRecord(formData))
return (formData: FormData.PersistedFormData) => parse(formData)
}

@@ -149,5 +120,5 @@

field: string
): (formData: globalThis.FormData) => Effect.Effect<never, FormData.FormDataError | ParseResult.ParseError, A>
): (formData: FormData.PersistedFormData) => Effect.Effect<never, FormData.FormDataError | ParseResult.ParseError, A>
(
formData: globalThis.FormData,
formData: FormData.PersistedFormData,
field: string

@@ -160,5 +131,7 @@ ): Effect.Effect<never, FormData.FormDataError | ParseResult.ParseError, A>

field: string
) => (formData: globalThis.FormData) => Effect.Effect<never, FormData.FormDataError | ParseResult.ParseError, A>,
) => (
formData: FormData.PersistedFormData
) => Effect.Effect<never, FormData.FormDataError | ParseResult.ParseError, A>,
(
formData: globalThis.FormData,
formData: FormData.PersistedFormData,
field: string

@@ -168,9 +141,9 @@ ) => Effect.Effect<never, FormData.FormDataError | ParseResult.ParseError, A>

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}`)

@@ -181,1 +154,286 @@ }),

}
/** @internal */
export const makeConfig = (
headers: Record<string, string>
): Effect.Effect<never, never, MP.BaseConfig> =>
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 = <IE>(
headers: Record<string, string>,
bufferSize = 16
): Channel.Channel<
never,
IE,
Chunk.Chunk<Uint8Array>,
unknown,
FormData.FormDataError | IE,
Chunk.Chunk<FormData.Part>,
unknown
> =>
Channel.acquireUseRelease(
Effect.all([
makeConfig(headers),
Queue.bounded<Chunk.Chunk<Uint8Array> | null>(bufferSize)
]),
([config, queue]) => makeFromQueue(config, queue),
([, queue]) => Queue.shutdown(queue)
)
const makeFromQueue = <IE>(
config: MP.BaseConfig,
queue: Queue.Queue<Chunk.Chunk<Uint8Array> | null>
): Channel.Channel<
never,
IE,
Chunk.Chunk<Uint8Array>,
unknown,
IE | FormData.FormDataError,
Chunk.Chunk<FormData.Part>,
unknown
> =>
Channel.suspend(() => {
let error = Option.none<Cause.Cause<IE | FormData.FormDataError>>()
let partsBuffer: Array<FormData.Part> = []
let partsFinished = false
const input: AsyncInput.AsyncInputProducer<IE, Chunk.Chunk<Uint8Array>, 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<Uint8Array> = []
let finished = false
const take: Channel.Channel<never, unknown, unknown, unknown, never, Chunk.Chunk<Uint8Array>, 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<FormData.Part>,
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<never, FormData.FormDataError, Uint8Array>
constructor(
info: MP.PartInfo,
channel: Channel.Channel<never, unknown, unknown, unknown, never, Chunk.Chunk<Uint8Array>, 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<never, FormData.FormDataError, FormData.Part>,
writeFile = defaultWriteFile
): Effect.Effect<FileSystem.FileSystem | Path.Path | Scope.Scope, FormData.FormDataError, FormData.PersistedFormData> =>
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, Array<FormData.PersistedFile> | 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<FormData.PersistedFile>).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
}
}

@@ -16,6 +16,3 @@ import type * as Schema from "@effect/schema/Schema"

/** @internal */
export const formDataRecord = Effect.map(
Effect.flatMap(serverRequestTag, (request) => request.formData),
FormData.toRecord
)
export const persistedFormData = Effect.flatMap(serverRequestTag, (request) => request.formData)

@@ -41,10 +38,7 @@ /** @internal */

/** @internal */
export const schemaFormData = <I extends Readonly<Record<string, string | ReadonlyArray<globalThis.File>>>, A>(
export const schemaFormData = <I extends FormData.PersistedFormData, A>(
schema: Schema.Schema<I, A>
) => {
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)
}

@@ -51,0 +45,0 @@

@@ -1,3 +0,4 @@

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"

@@ -4,0 +5,0 @@ import * as Deferred from "effect/Deferred"

/**
* @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"

@@ -7,0 +7,0 @@ import type * as Queue from "effect/Queue"

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc