@effect/platform 0.85.1 → 0.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/HttpApiBuilder.js +15 -5
- package/dist/cjs/HttpApiBuilder.js.map +1 -1
- package/dist/cjs/HttpApiSchema.js +8 -10
- package/dist/cjs/HttpApiSchema.js.map +1 -1
- package/dist/cjs/HttpIncomingMessage.js +7 -5
- package/dist/cjs/HttpIncomingMessage.js.map +1 -1
- package/dist/cjs/HttpServerRequest.js +10 -4
- package/dist/cjs/HttpServerRequest.js.map +1 -1
- package/dist/cjs/Multipart.js +400 -47
- package/dist/cjs/Multipart.js.map +1 -1
- package/dist/dts/HttpApiBuilder.d.ts.map +1 -1
- package/dist/dts/HttpApiSchema.d.ts +19 -4
- package/dist/dts/HttpApiSchema.d.ts.map +1 -1
- package/dist/dts/HttpIncomingMessage.d.ts +5 -2
- package/dist/dts/HttpIncomingMessage.d.ts.map +1 -1
- package/dist/dts/HttpServerRequest.d.ts +6 -1
- package/dist/dts/HttpServerRequest.d.ts.map +1 -1
- package/dist/dts/Multipart.d.ts +167 -88
- package/dist/dts/Multipart.d.ts.map +1 -1
- package/dist/esm/HttpApiBuilder.js +15 -5
- package/dist/esm/HttpApiBuilder.js.map +1 -1
- package/dist/esm/HttpApiSchema.js +8 -10
- package/dist/esm/HttpApiSchema.js.map +1 -1
- package/dist/esm/HttpIncomingMessage.js +5 -4
- package/dist/esm/HttpIncomingMessage.js.map +1 -1
- package/dist/esm/HttpServerRequest.js +6 -1
- package/dist/esm/HttpServerRequest.js.map +1 -1
- package/dist/esm/Multipart.js +385 -46
- package/dist/esm/Multipart.js.map +1 -1
- package/package.json +2 -2
- package/src/HttpApiBuilder.ts +16 -5
- package/src/HttpApiSchema.ts +25 -11
- package/src/HttpIncomingMessage.ts +5 -7
- package/src/HttpServerRequest.ts +6 -1
- package/src/Multipart.ts +632 -128
- package/dist/cjs/internal/multipart.js +0 -364
- package/dist/cjs/internal/multipart.js.map +0 -1
- package/dist/dts/internal/multipart.d.ts +0 -2
- package/dist/dts/internal/multipart.d.ts.map +0 -1
- package/dist/esm/internal/multipart.js +0 -347
- package/dist/esm/internal/multipart.js.map +0 -1
- package/src/internal/multipart.ts +0 -491
|
@@ -1,491 +0,0 @@
|
|
|
1
|
-
import type { Cause } from "effect/Cause"
|
|
2
|
-
import * as Channel from "effect/Channel"
|
|
3
|
-
import * as Chunk from "effect/Chunk"
|
|
4
|
-
import * as Effect from "effect/Effect"
|
|
5
|
-
import * as Exit from "effect/Exit"
|
|
6
|
-
import * as FiberRef from "effect/FiberRef"
|
|
7
|
-
import { dual } from "effect/Function"
|
|
8
|
-
import { globalValue } from "effect/GlobalValue"
|
|
9
|
-
import * as Inspectable from "effect/Inspectable"
|
|
10
|
-
import * as Mailbox from "effect/Mailbox"
|
|
11
|
-
import * as Option from "effect/Option"
|
|
12
|
-
import type * as ParseResult from "effect/ParseResult"
|
|
13
|
-
import * as Predicate from "effect/Predicate"
|
|
14
|
-
import * as Schema from "effect/Schema"
|
|
15
|
-
import type { ParseOptions } from "effect/SchemaAST"
|
|
16
|
-
import type * as Scope from "effect/Scope"
|
|
17
|
-
import type * as AsyncInput from "effect/SingleProducerAsyncInput"
|
|
18
|
-
import * as Stream from "effect/Stream"
|
|
19
|
-
import * as MP from "multipasta"
|
|
20
|
-
import { TypeIdError } from "../Error.js"
|
|
21
|
-
import * as FileSystem from "../FileSystem.js"
|
|
22
|
-
import * as IncomingMessage from "../HttpIncomingMessage.js"
|
|
23
|
-
import type * as Multipart from "../Multipart.js"
|
|
24
|
-
import * as Path from "../Path.js"
|
|
25
|
-
|
|
26
|
-
/** @internal */
|
|
27
|
-
export const TypeId: Multipart.TypeId = Symbol.for("@effect/platform/Multipart") as Multipart.TypeId
|
|
28
|
-
|
|
29
|
-
/** @internal */
|
|
30
|
-
export const isPart = (u: unknown): u is Multipart.Part => Predicate.hasProperty(u, TypeId)
|
|
31
|
-
|
|
32
|
-
/** @internal */
|
|
33
|
-
export const isField = (u: unknown): u is Multipart.Field => isPart(u) && u._tag === "Field"
|
|
34
|
-
|
|
35
|
-
/** @internal */
|
|
36
|
-
export const isFile = (u: unknown): u is Multipart.File => isPart(u) && u._tag === "File"
|
|
37
|
-
|
|
38
|
-
/** @internal */
|
|
39
|
-
export const isPersistedFile = (u: unknown): u is Multipart.PersistedFile =>
|
|
40
|
-
Predicate.hasProperty(u, TypeId) && Predicate.isTagged(u, "PersistedFile")
|
|
41
|
-
|
|
42
|
-
/** @internal */
|
|
43
|
-
export const ErrorTypeId: Multipart.ErrorTypeId = Symbol.for(
|
|
44
|
-
"@effect/platform/Multipart/MultipartError"
|
|
45
|
-
) as Multipart.ErrorTypeId
|
|
46
|
-
|
|
47
|
-
/** @internal */
|
|
48
|
-
export class MultipartError extends TypeIdError(ErrorTypeId, "MultipartError")<{
|
|
49
|
-
readonly reason: "FileTooLarge" | "FieldTooLarge" | "BodyTooLarge" | "TooManyParts" | "InternalError" | "Parse"
|
|
50
|
-
readonly cause: unknown
|
|
51
|
-
}> {
|
|
52
|
-
get message(): string {
|
|
53
|
-
return this.reason
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** @internal */
|
|
58
|
-
export const maxParts: FiberRef.FiberRef<Option.Option<number>> = globalValue(
|
|
59
|
-
"@effect/platform/Multipart/maxParts",
|
|
60
|
-
() => FiberRef.unsafeMake(Option.none<number>())
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
/** @internal */
|
|
64
|
-
export const withMaxParts = dual<
|
|
65
|
-
(count: Option.Option<number>) => <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>,
|
|
66
|
-
<A, E, R>(effect: Effect.Effect<A, E, R>, count: Option.Option<number>) => Effect.Effect<A, E, R>
|
|
67
|
-
>(2, (effect, count) => Effect.locally(effect, maxParts, count))
|
|
68
|
-
|
|
69
|
-
/** @internal */
|
|
70
|
-
export const maxFieldSize: FiberRef.FiberRef<FileSystem.Size> = globalValue(
|
|
71
|
-
"@effect/platform/Multipart/maxFieldSize",
|
|
72
|
-
() => FiberRef.unsafeMake(FileSystem.Size(10 * 1024 * 1024))
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
/** @internal */
|
|
76
|
-
export const withMaxFieldSize = dual<
|
|
77
|
-
(size: FileSystem.SizeInput) => <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>,
|
|
78
|
-
<A, E, R>(effect: Effect.Effect<A, E, R>, size: FileSystem.SizeInput) => Effect.Effect<A, E, R>
|
|
79
|
-
>(2, (effect, size) => Effect.locally(effect, maxFieldSize, FileSystem.Size(size)))
|
|
80
|
-
|
|
81
|
-
/** @internal */
|
|
82
|
-
export const maxFileSize: FiberRef.FiberRef<Option.Option<FileSystem.Size>> = globalValue(
|
|
83
|
-
"@effect/platform/Multipart/maxFileSize",
|
|
84
|
-
() => FiberRef.unsafeMake(Option.none<FileSystem.Size>())
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
/** @internal */
|
|
88
|
-
export const withMaxFileSize = dual<
|
|
89
|
-
(size: Option.Option<FileSystem.SizeInput>) => <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>,
|
|
90
|
-
<A, E, R>(effect: Effect.Effect<A, E, R>, size: Option.Option<FileSystem.SizeInput>) => Effect.Effect<A, E, R>
|
|
91
|
-
>(2, (effect, size) => Effect.locally(effect, maxFileSize, Option.map(size, FileSystem.Size)))
|
|
92
|
-
|
|
93
|
-
/** @internal */
|
|
94
|
-
export const fieldMimeTypes: FiberRef.FiberRef<Chunk.Chunk<string>> = globalValue(
|
|
95
|
-
"@effect/platform/Multipart/fieldMimeTypes",
|
|
96
|
-
() => FiberRef.unsafeMake<Chunk.Chunk<string>>(Chunk.make("application/json"))
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
/** @internal */
|
|
100
|
-
export const withFieldMimeTypes = dual<
|
|
101
|
-
(mimeTypes: ReadonlyArray<string>) => <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>,
|
|
102
|
-
<A, E, R>(effect: Effect.Effect<A, E, R>, mimeTypes: ReadonlyArray<string>) => Effect.Effect<A, E, R>
|
|
103
|
-
>(2, (effect, mimeTypes) => Effect.locally(effect, fieldMimeTypes, Chunk.fromIterable(mimeTypes)))
|
|
104
|
-
|
|
105
|
-
/** @internal */
|
|
106
|
-
export const FileSchema: Schema.Schema<Multipart.PersistedFile> = Schema.declare(isPersistedFile, {
|
|
107
|
-
identifier: "PersistedFile",
|
|
108
|
-
jsonSchema: {
|
|
109
|
-
type: "string",
|
|
110
|
-
format: "binary"
|
|
111
|
-
}
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
/** @internal */
|
|
115
|
-
export const FilesSchema: Schema.Schema<ReadonlyArray<Multipart.PersistedFile>> = Schema.Array(FileSchema)
|
|
116
|
-
|
|
117
|
-
/** @internal */
|
|
118
|
-
export const SingleFileSchema: Schema.transform<
|
|
119
|
-
Schema.Schema<ReadonlyArray<Multipart.PersistedFile>>,
|
|
120
|
-
Schema.Schema<Multipart.PersistedFile>
|
|
121
|
-
> = Schema.transform(FilesSchema.pipe(Schema.itemsCount(1)), FileSchema, {
|
|
122
|
-
strict: true,
|
|
123
|
-
decode: ([file]) => file,
|
|
124
|
-
encode: (file) => [file]
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
/** @internal */
|
|
128
|
-
export const schemaPersisted = <A, I extends Partial<Multipart.Persisted>, R>(
|
|
129
|
-
schema: Schema.Schema<A, I, R>,
|
|
130
|
-
options?: ParseOptions | undefined
|
|
131
|
-
) => {
|
|
132
|
-
const parse = Schema.decodeUnknown(schema, options)
|
|
133
|
-
return (persisted: Multipart.Persisted) => parse(persisted)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/** @internal */
|
|
137
|
-
export const schemaJson = <A, I, R>(schema: Schema.Schema<A, I, R>, options?: ParseOptions | undefined): {
|
|
138
|
-
(
|
|
139
|
-
field: string
|
|
140
|
-
): (persisted: Multipart.Persisted) => Effect.Effect<A, ParseResult.ParseError, R>
|
|
141
|
-
(
|
|
142
|
-
persisted: Multipart.Persisted,
|
|
143
|
-
field: string
|
|
144
|
-
): Effect.Effect<A, ParseResult.ParseError, R>
|
|
145
|
-
} => {
|
|
146
|
-
const fromJson = Schema.parseJson(schema)
|
|
147
|
-
return dual<
|
|
148
|
-
(
|
|
149
|
-
field: string
|
|
150
|
-
) => (
|
|
151
|
-
persisted: Multipart.Persisted
|
|
152
|
-
) => Effect.Effect<A, ParseResult.ParseError, R>,
|
|
153
|
-
(
|
|
154
|
-
persisted: Multipart.Persisted,
|
|
155
|
-
field: string
|
|
156
|
-
) => Effect.Effect<A, ParseResult.ParseError, R>
|
|
157
|
-
>(2, (persisted, field) =>
|
|
158
|
-
Effect.map(
|
|
159
|
-
Schema.decodeUnknown(
|
|
160
|
-
Schema.Struct({
|
|
161
|
-
[field]: fromJson
|
|
162
|
-
}),
|
|
163
|
-
options
|
|
164
|
-
)(persisted),
|
|
165
|
-
(_) => _[field]
|
|
166
|
-
))
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/** @internal */
|
|
170
|
-
export const makeConfig = (
|
|
171
|
-
headers: Record<string, string>
|
|
172
|
-
): Effect.Effect<MP.BaseConfig> =>
|
|
173
|
-
Effect.withFiberRuntime((fiber) => {
|
|
174
|
-
const mimeTypes = fiber.getFiberRef(fieldMimeTypes)
|
|
175
|
-
return Effect.succeed<MP.BaseConfig>({
|
|
176
|
-
headers,
|
|
177
|
-
maxParts: Option.getOrUndefined(fiber.getFiberRef(maxParts)),
|
|
178
|
-
maxFieldSize: Number(fiber.getFiberRef(maxFieldSize)),
|
|
179
|
-
maxPartSize: fiber.getFiberRef(maxFileSize).pipe(Option.map(Number), Option.getOrUndefined),
|
|
180
|
-
maxTotalSize: fiber.getFiberRef(IncomingMessage.maxBodySize).pipe(Option.map(Number), Option.getOrUndefined),
|
|
181
|
-
isFile: mimeTypes.length === 0 ? undefined : (info: MP.PartInfo): boolean =>
|
|
182
|
-
!Chunk.some(
|
|
183
|
-
mimeTypes,
|
|
184
|
-
(_) => info.contentType.includes(_)
|
|
185
|
-
) && MP.defaultIsFile(info)
|
|
186
|
-
})
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
/** @internal */
|
|
190
|
-
export const makeChannel = <IE>(
|
|
191
|
-
headers: Record<string, string>,
|
|
192
|
-
bufferSize = 16
|
|
193
|
-
): Channel.Channel<
|
|
194
|
-
Chunk.Chunk<Multipart.Part>,
|
|
195
|
-
Chunk.Chunk<Uint8Array>,
|
|
196
|
-
Multipart.MultipartError | IE,
|
|
197
|
-
IE,
|
|
198
|
-
unknown,
|
|
199
|
-
unknown
|
|
200
|
-
> =>
|
|
201
|
-
Channel.acquireUseRelease(
|
|
202
|
-
Effect.all([
|
|
203
|
-
makeConfig(headers),
|
|
204
|
-
Mailbox.make<Chunk.Chunk<Uint8Array>>(bufferSize)
|
|
205
|
-
]),
|
|
206
|
-
([config, mailbox]) => {
|
|
207
|
-
let partsBuffer: Array<Multipart.Part> = []
|
|
208
|
-
let exit = Option.none<Exit.Exit<void, IE | Multipart.MultipartError>>()
|
|
209
|
-
|
|
210
|
-
const input: AsyncInput.AsyncInputProducer<IE, Chunk.Chunk<Uint8Array>, unknown> = {
|
|
211
|
-
awaitRead: () => Effect.void,
|
|
212
|
-
emit(element) {
|
|
213
|
-
return mailbox.offer(element)
|
|
214
|
-
},
|
|
215
|
-
error(cause) {
|
|
216
|
-
exit = Option.some(Exit.failCause(cause))
|
|
217
|
-
return mailbox.end
|
|
218
|
-
},
|
|
219
|
-
done(_value) {
|
|
220
|
-
return mailbox.end
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const parser = MP.make({
|
|
225
|
-
...config,
|
|
226
|
-
onField(info, value) {
|
|
227
|
-
partsBuffer.push(new FieldImpl(info.name, info.contentType, MP.decodeField(info, value)))
|
|
228
|
-
},
|
|
229
|
-
onFile(info) {
|
|
230
|
-
let chunks: Array<Uint8Array> = []
|
|
231
|
-
let finished = false
|
|
232
|
-
const take: Channel.Channel<Chunk.Chunk<Uint8Array>> = Channel.suspend(() => {
|
|
233
|
-
if (chunks.length === 0) {
|
|
234
|
-
return finished ? Channel.void : Channel.zipRight(pump, take)
|
|
235
|
-
}
|
|
236
|
-
const chunk = Chunk.unsafeFromArray(chunks)
|
|
237
|
-
chunks = []
|
|
238
|
-
return finished ? Channel.write(chunk) : Channel.zipRight(
|
|
239
|
-
Channel.write(chunk),
|
|
240
|
-
Channel.zipRight(pump, take)
|
|
241
|
-
)
|
|
242
|
-
})
|
|
243
|
-
partsBuffer.push(new FileImpl(info, take))
|
|
244
|
-
return function(chunk) {
|
|
245
|
-
if (chunk === null) {
|
|
246
|
-
finished = true
|
|
247
|
-
} else {
|
|
248
|
-
chunks.push(chunk)
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
},
|
|
252
|
-
onError(error_) {
|
|
253
|
-
exit = Option.some(Exit.fail(convertError(error_)))
|
|
254
|
-
},
|
|
255
|
-
onDone() {
|
|
256
|
-
exit = Option.some(Exit.void)
|
|
257
|
-
}
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
const pump = Channel.flatMap(
|
|
261
|
-
mailbox.takeAll,
|
|
262
|
-
([chunks, done]) =>
|
|
263
|
-
Channel.sync(() => {
|
|
264
|
-
Chunk.forEach(chunks, Chunk.forEach(parser.write))
|
|
265
|
-
if (done) {
|
|
266
|
-
parser.end()
|
|
267
|
-
}
|
|
268
|
-
})
|
|
269
|
-
)
|
|
270
|
-
|
|
271
|
-
const partsChannel: Channel.Channel<
|
|
272
|
-
Chunk.Chunk<Multipart.Part>,
|
|
273
|
-
unknown,
|
|
274
|
-
IE | Multipart.MultipartError
|
|
275
|
-
> = Channel.flatMap(
|
|
276
|
-
pump,
|
|
277
|
-
() => {
|
|
278
|
-
if (partsBuffer.length === 0) {
|
|
279
|
-
return exit._tag === "None" ? partsChannel : writeExit(exit.value)
|
|
280
|
-
}
|
|
281
|
-
const chunk = Chunk.unsafeFromArray(partsBuffer)
|
|
282
|
-
partsBuffer = []
|
|
283
|
-
return Channel.zipRight(
|
|
284
|
-
Channel.write(chunk),
|
|
285
|
-
exit._tag === "None" ? partsChannel : writeExit(exit.value)
|
|
286
|
-
)
|
|
287
|
-
}
|
|
288
|
-
)
|
|
289
|
-
|
|
290
|
-
return Channel.embedInput(partsChannel, input)
|
|
291
|
-
},
|
|
292
|
-
([, mailbox]) => mailbox.shutdown
|
|
293
|
-
)
|
|
294
|
-
|
|
295
|
-
const writeExit = <A, E>(
|
|
296
|
-
self: Exit.Exit<A, E>
|
|
297
|
-
): Channel.Channel<never, unknown, E> => self._tag === "Success" ? Channel.void : Channel.failCause(self.cause)
|
|
298
|
-
|
|
299
|
-
function convertError(cause: MP.MultipartError): Multipart.MultipartError {
|
|
300
|
-
switch (cause._tag) {
|
|
301
|
-
case "ReachedLimit": {
|
|
302
|
-
switch (cause.limit) {
|
|
303
|
-
case "MaxParts": {
|
|
304
|
-
return new MultipartError({ reason: "TooManyParts", cause })
|
|
305
|
-
}
|
|
306
|
-
case "MaxFieldSize": {
|
|
307
|
-
return new MultipartError({ reason: "FieldTooLarge", cause })
|
|
308
|
-
}
|
|
309
|
-
case "MaxPartSize": {
|
|
310
|
-
return new MultipartError({ reason: "FileTooLarge", cause })
|
|
311
|
-
}
|
|
312
|
-
case "MaxTotalSize": {
|
|
313
|
-
return new MultipartError({ reason: "BodyTooLarge", cause })
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
default: {
|
|
318
|
-
return new MultipartError({ reason: "Parse", cause })
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
abstract class PartBase extends Inspectable.Class {
|
|
324
|
-
readonly [TypeId]: Multipart.TypeId
|
|
325
|
-
constructor() {
|
|
326
|
-
super()
|
|
327
|
-
this[TypeId] = TypeId
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
class FieldImpl extends PartBase implements Multipart.Field {
|
|
332
|
-
readonly _tag = "Field"
|
|
333
|
-
|
|
334
|
-
constructor(
|
|
335
|
-
readonly key: string,
|
|
336
|
-
readonly contentType: string,
|
|
337
|
-
readonly value: string
|
|
338
|
-
) {
|
|
339
|
-
super()
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
toJSON(): unknown {
|
|
343
|
-
return {
|
|
344
|
-
_id: "@effect/platform/Multipart/Part",
|
|
345
|
-
_tag: "Field",
|
|
346
|
-
key: this.key,
|
|
347
|
-
contentType: this.contentType,
|
|
348
|
-
value: this.value
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
class FileImpl extends PartBase implements Multipart.File {
|
|
354
|
-
readonly _tag = "File"
|
|
355
|
-
readonly key: string
|
|
356
|
-
readonly name: string
|
|
357
|
-
readonly contentType: string
|
|
358
|
-
readonly content: Stream.Stream<Uint8Array, Multipart.MultipartError>
|
|
359
|
-
readonly contentEffect: Effect.Effect<Uint8Array, Multipart.MultipartError>
|
|
360
|
-
|
|
361
|
-
constructor(
|
|
362
|
-
info: MP.PartInfo,
|
|
363
|
-
channel: Channel.Channel<Chunk.Chunk<Uint8Array>, unknown, never, unknown, void, unknown>
|
|
364
|
-
) {
|
|
365
|
-
super()
|
|
366
|
-
this.key = info.name
|
|
367
|
-
this.name = info.filename ?? info.name
|
|
368
|
-
this.contentType = info.contentType
|
|
369
|
-
this.content = Stream.fromChannel(channel)
|
|
370
|
-
this.contentEffect = channel.pipe(
|
|
371
|
-
Channel.pipeTo(collectUint8Array),
|
|
372
|
-
Channel.run,
|
|
373
|
-
Effect.mapError((cause) => new MultipartError({ reason: "InternalError", cause }))
|
|
374
|
-
)
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
toJSON(): unknown {
|
|
378
|
-
return {
|
|
379
|
-
_id: "@effect/platform/Multipart/Part",
|
|
380
|
-
_tag: "File",
|
|
381
|
-
key: this.key,
|
|
382
|
-
name: this.name,
|
|
383
|
-
contentType: this.contentType
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
const defaultWriteFile = (path: string, file: Multipart.File) =>
|
|
389
|
-
Effect.flatMap(
|
|
390
|
-
FileSystem.FileSystem,
|
|
391
|
-
(fs) =>
|
|
392
|
-
Effect.mapError(
|
|
393
|
-
Stream.run(file.content, fs.sink(path)),
|
|
394
|
-
(cause) => new MultipartError({ reason: "InternalError", cause })
|
|
395
|
-
)
|
|
396
|
-
)
|
|
397
|
-
|
|
398
|
-
/** @internal */
|
|
399
|
-
export const collectUint8Array = Channel.suspend(() => {
|
|
400
|
-
let accumulator = new Uint8Array(0)
|
|
401
|
-
const loop: Channel.Channel<
|
|
402
|
-
never,
|
|
403
|
-
Chunk.Chunk<Uint8Array>,
|
|
404
|
-
unknown,
|
|
405
|
-
unknown,
|
|
406
|
-
Uint8Array
|
|
407
|
-
> = Channel.readWithCause({
|
|
408
|
-
onInput(chunk: Chunk.Chunk<Uint8Array>) {
|
|
409
|
-
for (const element of chunk) {
|
|
410
|
-
const newAccumulator = new Uint8Array(accumulator.length + element.length)
|
|
411
|
-
newAccumulator.set(accumulator, 0)
|
|
412
|
-
newAccumulator.set(element, accumulator.length)
|
|
413
|
-
accumulator = newAccumulator
|
|
414
|
-
}
|
|
415
|
-
return loop
|
|
416
|
-
},
|
|
417
|
-
onFailure: (cause: Cause<unknown>) => Channel.failCause(cause),
|
|
418
|
-
onDone: () => Channel.succeed(accumulator)
|
|
419
|
-
})
|
|
420
|
-
return loop
|
|
421
|
-
})
|
|
422
|
-
|
|
423
|
-
/** @internal */
|
|
424
|
-
export const toPersisted = (
|
|
425
|
-
stream: Stream.Stream<Multipart.Part, Multipart.MultipartError>,
|
|
426
|
-
writeFile = defaultWriteFile
|
|
427
|
-
): Effect.Effect<Multipart.Persisted, Multipart.MultipartError, FileSystem.FileSystem | Path.Path | Scope.Scope> =>
|
|
428
|
-
Effect.gen(function*() {
|
|
429
|
-
const fs = yield* FileSystem.FileSystem
|
|
430
|
-
const path_ = yield* Path.Path
|
|
431
|
-
const dir = yield* fs.makeTempDirectoryScoped()
|
|
432
|
-
const persisted: Record<string, Array<Multipart.PersistedFile> | Array<string> | string> = Object.create(null)
|
|
433
|
-
yield* Stream.runForEach(stream, (part) => {
|
|
434
|
-
if (part._tag === "Field") {
|
|
435
|
-
if (!(part.key in persisted)) {
|
|
436
|
-
persisted[part.key] = part.value
|
|
437
|
-
} else if (typeof persisted[part.key] === "string") {
|
|
438
|
-
persisted[part.key] = [persisted[part.key] as string, part.value]
|
|
439
|
-
} else {
|
|
440
|
-
;(persisted[part.key] as Array<string>).push(part.value)
|
|
441
|
-
}
|
|
442
|
-
return Effect.void
|
|
443
|
-
} else if (part.name === "") {
|
|
444
|
-
return Effect.void
|
|
445
|
-
}
|
|
446
|
-
const file = part
|
|
447
|
-
const path = path_.join(dir, path_.basename(file.name).slice(-128))
|
|
448
|
-
const filePart = new PersistedFileImpl(
|
|
449
|
-
file.key,
|
|
450
|
-
file.name,
|
|
451
|
-
file.contentType,
|
|
452
|
-
path
|
|
453
|
-
)
|
|
454
|
-
if (Array.isArray(persisted[part.key])) {
|
|
455
|
-
;(persisted[part.key] as Array<Multipart.PersistedFile>).push(filePart)
|
|
456
|
-
} else {
|
|
457
|
-
persisted[part.key] = [filePart]
|
|
458
|
-
}
|
|
459
|
-
return writeFile(path, file)
|
|
460
|
-
})
|
|
461
|
-
return persisted
|
|
462
|
-
}).pipe(
|
|
463
|
-
Effect.catchTags({
|
|
464
|
-
SystemError: (cause) => Effect.fail(new MultipartError({ reason: "InternalError", cause })),
|
|
465
|
-
BadArgument: (cause) => Effect.fail(new MultipartError({ reason: "InternalError", cause }))
|
|
466
|
-
})
|
|
467
|
-
)
|
|
468
|
-
|
|
469
|
-
class PersistedFileImpl extends PartBase implements Multipart.PersistedFile {
|
|
470
|
-
readonly _tag = "PersistedFile"
|
|
471
|
-
|
|
472
|
-
constructor(
|
|
473
|
-
readonly key: string,
|
|
474
|
-
readonly name: string,
|
|
475
|
-
readonly contentType: string,
|
|
476
|
-
readonly path: string
|
|
477
|
-
) {
|
|
478
|
-
super()
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
toJSON(): unknown {
|
|
482
|
-
return {
|
|
483
|
-
_id: "@effect/platform/Multipart/Part",
|
|
484
|
-
_tag: "PersistedFile",
|
|
485
|
-
key: this.key,
|
|
486
|
-
name: this.name,
|
|
487
|
-
contentType: this.contentType,
|
|
488
|
-
path: this.path
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|