@effect/platform 0.85.2 → 0.87.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.
Files changed (49) hide show
  1. package/dist/cjs/HttpApiBuilder.js +15 -5
  2. package/dist/cjs/HttpApiBuilder.js.map +1 -1
  3. package/dist/cjs/HttpApiClient.js +17 -8
  4. package/dist/cjs/HttpApiClient.js.map +1 -1
  5. package/dist/cjs/HttpApiSchema.js +6 -6
  6. package/dist/cjs/HttpApiSchema.js.map +1 -1
  7. package/dist/cjs/HttpIncomingMessage.js +7 -5
  8. package/dist/cjs/HttpIncomingMessage.js.map +1 -1
  9. package/dist/cjs/HttpServerRequest.js +10 -4
  10. package/dist/cjs/HttpServerRequest.js.map +1 -1
  11. package/dist/cjs/Multipart.js +400 -47
  12. package/dist/cjs/Multipart.js.map +1 -1
  13. package/dist/dts/HttpApiBuilder.d.ts.map +1 -1
  14. package/dist/dts/HttpApiClient.d.ts +28 -15
  15. package/dist/dts/HttpApiClient.d.ts.map +1 -1
  16. package/dist/dts/HttpApiSchema.d.ts +19 -4
  17. package/dist/dts/HttpApiSchema.d.ts.map +1 -1
  18. package/dist/dts/HttpIncomingMessage.d.ts +5 -2
  19. package/dist/dts/HttpIncomingMessage.d.ts.map +1 -1
  20. package/dist/dts/HttpServerRequest.d.ts +6 -1
  21. package/dist/dts/HttpServerRequest.d.ts.map +1 -1
  22. package/dist/dts/Multipart.d.ts +167 -88
  23. package/dist/dts/Multipart.d.ts.map +1 -1
  24. package/dist/esm/HttpApiBuilder.js +15 -5
  25. package/dist/esm/HttpApiBuilder.js.map +1 -1
  26. package/dist/esm/HttpApiClient.js +14 -6
  27. package/dist/esm/HttpApiClient.js.map +1 -1
  28. package/dist/esm/HttpApiSchema.js +6 -6
  29. package/dist/esm/HttpApiSchema.js.map +1 -1
  30. package/dist/esm/HttpIncomingMessage.js +5 -4
  31. package/dist/esm/HttpIncomingMessage.js.map +1 -1
  32. package/dist/esm/HttpServerRequest.js +6 -1
  33. package/dist/esm/HttpServerRequest.js.map +1 -1
  34. package/dist/esm/Multipart.js +385 -46
  35. package/dist/esm/Multipart.js.map +1 -1
  36. package/package.json +2 -2
  37. package/src/HttpApiBuilder.ts +16 -5
  38. package/src/HttpApiClient.ts +63 -32
  39. package/src/HttpApiSchema.ts +23 -7
  40. package/src/HttpIncomingMessage.ts +5 -7
  41. package/src/HttpServerRequest.ts +6 -1
  42. package/src/Multipart.ts +632 -128
  43. package/dist/cjs/internal/multipart.js +0 -364
  44. package/dist/cjs/internal/multipart.js.map +0 -1
  45. package/dist/dts/internal/multipart.d.ts +0 -2
  46. package/dist/dts/internal/multipart.d.ts.map +0 -1
  47. package/dist/esm/internal/multipart.js +0 -347
  48. package/dist/esm/internal/multipart.js.map +0 -1
  49. package/src/internal/multipart.ts +0 -491
package/src/Multipart.ts CHANGED
@@ -1,28 +1,33 @@
1
1
  /**
2
2
  * @since 1.0.0
3
3
  */
4
- import type { YieldableError } from "effect/Cause"
5
- import type * as Channel from "effect/Channel"
6
- import type * as Chunk from "effect/Chunk"
7
- import type * as Effect from "effect/Effect"
8
- import type * as FiberRef from "effect/FiberRef"
9
- import type { Inspectable } from "effect/Inspectable"
10
- import type * as Option from "effect/Option"
4
+ import type * as Cause from "effect/Cause"
5
+ import * as Channel from "effect/Channel"
6
+ import * as Chunk from "effect/Chunk"
7
+ import * as Context from "effect/Context"
8
+ import * as Effect from "effect/Effect"
9
+ import * as Exit from "effect/Exit"
10
+ import { constant, dual } from "effect/Function"
11
+ import * as Inspectable from "effect/Inspectable"
12
+ import * as Mailbox from "effect/Mailbox"
13
+ import * as Option from "effect/Option"
11
14
  import type * as ParseResult from "effect/ParseResult"
12
- import type * as Schema from "effect/Schema"
15
+ import * as Predicate from "effect/Predicate"
16
+ import * as Schema from "effect/Schema"
13
17
  import type { ParseOptions } from "effect/SchemaAST"
14
18
  import type * as Scope from "effect/Scope"
15
- import type * as Stream from "effect/Stream"
16
- import type * as Multipasta from "multipasta"
17
- import type * as FileSystem from "./FileSystem.js"
18
- import * as internal from "./internal/multipart.js"
19
- import type * as Path from "./Path.js"
19
+ import type * as AsyncInput from "effect/SingleProducerAsyncInput"
20
+ import * as Stream from "effect/Stream"
21
+ import * as MP from "multipasta"
22
+ import * as FileSystem from "./FileSystem.js"
23
+ import * as IncomingMessage from "./HttpIncomingMessage.js"
24
+ import * as Path from "./Path.js"
20
25
 
21
26
  /**
22
27
  * @since 1.0.0
23
28
  * @category type ids
24
29
  */
25
- export const TypeId: unique symbol = internal.TypeId
30
+ export const TypeId: unique symbol = Symbol.for("@effect/platform/Multipart")
26
31
 
27
32
  /**
28
33
  * @since 1.0.0
@@ -36,12 +41,6 @@ export type TypeId = typeof TypeId
36
41
  */
37
42
  export type Part = Field | File
38
43
 
39
- /**
40
- * @since 1.0.0
41
- * @category refinements
42
- */
43
- export const isPart: (u: unknown) => u is Part = internal.isPart
44
-
45
44
  /**
46
45
  * @since 1.0.0
47
46
  */
@@ -50,7 +49,7 @@ export declare namespace Part {
50
49
  * @since 1.0.0
51
50
  * @category models
52
51
  */
53
- export interface Proto extends Inspectable {
52
+ export interface Proto extends Inspectable.Inspectable {
54
53
  readonly [TypeId]: TypeId
55
54
  readonly _tag: string
56
55
  }
@@ -69,9 +68,15 @@ export interface Field extends Part.Proto {
69
68
 
70
69
  /**
71
70
  * @since 1.0.0
72
- * @category refinements
71
+ * @category Guards
73
72
  */
74
- export const isField: (u: unknown) => u is Field = internal.isField
73
+ export const isPart = (u: unknown): u is Part => Predicate.hasProperty(u, TypeId)
74
+
75
+ /**
76
+ * @since 1.0.0
77
+ * @category Guards
78
+ */
79
+ export const isField = (u: unknown): u is Field => isPart(u) && u._tag === "Field"
75
80
 
76
81
  /**
77
82
  * @since 1.0.0
@@ -88,9 +93,9 @@ export interface File extends Part.Proto {
88
93
 
89
94
  /**
90
95
  * @since 1.0.0
91
- * @category refinements
96
+ * @category Guards
92
97
  */
93
- export const isFile: (u: unknown) => u is File = internal.isFile
98
+ export const isFile = (u: unknown): u is File => isPart(u) && u._tag === "File"
94
99
 
95
100
  /**
96
101
  * @since 1.0.0
@@ -106,9 +111,10 @@ export interface PersistedFile extends Part.Proto {
106
111
 
107
112
  /**
108
113
  * @since 1.0.0
109
- * @category refinements
114
+ * @category Guards
110
115
  */
111
- export const isPersistedFile: (u: unknown) => u is PersistedFile = internal.isPersistedFile
116
+ export const isPersistedFile = (u: unknown): u is PersistedFile =>
117
+ Predicate.hasProperty(u, TypeId) && Predicate.isTagged(u, "PersistedFile")
112
118
 
113
119
  /**
114
120
  * @since 1.0.0
@@ -120,44 +126,594 @@ export interface Persisted {
120
126
 
121
127
  /**
122
128
  * @since 1.0.0
123
- * @category type ids
129
+ * @category Errors
124
130
  */
125
- export const ErrorTypeId: unique symbol = internal.ErrorTypeId
131
+ export const ErrorTypeId: unique symbol = Symbol.for(
132
+ "@effect/platform/Multipart/MultipartError"
133
+ )
126
134
 
127
135
  /**
128
136
  * @since 1.0.0
129
- * @category type ids
137
+ * @category Errors
130
138
  */
131
139
  export type ErrorTypeId = typeof ErrorTypeId
132
140
 
133
141
  /**
134
142
  * @since 1.0.0
135
- * @category errors
143
+ * @category Errors
136
144
  */
137
- export interface MultipartError extends YieldableError {
138
- readonly [ErrorTypeId]: ErrorTypeId
139
- readonly _tag: "MultipartError"
140
- readonly reason: "FileTooLarge" | "FieldTooLarge" | "BodyTooLarge" | "TooManyParts" | "InternalError" | "Parse"
141
- readonly message: string
142
- readonly cause: unknown
145
+ export class MultipartError extends Schema.TaggedError<MultipartError>()("MultipartError", {
146
+ reason: Schema.Literal("FileTooLarge", "FieldTooLarge", "BodyTooLarge", "TooManyParts", "InternalError", "Parse"),
147
+ cause: Schema.Defect
148
+ }) {
149
+ /**
150
+ * @since 1.0.0
151
+ */
152
+ readonly [ErrorTypeId]: ErrorTypeId = ErrorTypeId
153
+
154
+ /**
155
+ * @since 1.0.0
156
+ */
157
+ get message(): string {
158
+ return this.reason
159
+ }
143
160
  }
144
161
 
145
162
  /**
146
163
  * @since 1.0.0
147
- * @category errors
164
+ * @category Schemas
148
165
  */
149
- export const MultipartError: new(
166
+ export const FileSchema: Schema.Schema<PersistedFile> = Schema.declare(isPersistedFile, {
167
+ identifier: "PersistedFile",
168
+ jsonSchema: {
169
+ type: "string",
170
+ format: "binary"
171
+ }
172
+ })
173
+
174
+ /**
175
+ * @since 1.0.0
176
+ * @category Schemas
177
+ */
178
+ export const FilesSchema: Schema.Schema<ReadonlyArray<PersistedFile>> = Schema.Array(FileSchema)
179
+
180
+ /**
181
+ * @since 1.0.0
182
+ * @category Schemas
183
+ */
184
+ export const SingleFileSchema: Schema.transform<
185
+ Schema.Schema<ReadonlyArray<PersistedFile>>,
186
+ Schema.Schema<PersistedFile>
187
+ > = Schema.transform(FilesSchema.pipe(Schema.itemsCount(1)), FileSchema, {
188
+ strict: true,
189
+ decode: ([file]) => file,
190
+ encode: (file) => [file]
191
+ })
192
+
193
+ /**
194
+ * @since 1.0.0
195
+ * @category Schemas
196
+ */
197
+ export const schemaPersisted = <A, I extends Partial<Persisted>, R>(
198
+ schema: Schema.Schema<A, I, R>,
199
+ options?: ParseOptions | undefined
200
+ ): (persisted: Persisted) => Effect.Effect<
201
+ A,
202
+ ParseResult.ParseError,
203
+ R
204
+ > => Schema.decodeUnknown(schema, options)
205
+
206
+ /**
207
+ * @since 1.0.0
208
+ * @category Schemas
209
+ */
210
+ export const schemaJson = <A, I, R>(schema: Schema.Schema<A, I, R>, options?: ParseOptions | undefined): {
211
+ (
212
+ field: string
213
+ ): (persisted: Persisted) => Effect.Effect<A, ParseResult.ParseError, R>
214
+ (
215
+ persisted: Persisted,
216
+ field: string
217
+ ): Effect.Effect<A, ParseResult.ParseError, R>
218
+ } => {
219
+ const fromJson = Schema.parseJson(schema)
220
+ return dual<
221
+ (
222
+ field: string
223
+ ) => (
224
+ persisted: Persisted
225
+ ) => Effect.Effect<A, ParseResult.ParseError, R>,
226
+ (
227
+ persisted: Persisted,
228
+ field: string
229
+ ) => Effect.Effect<A, ParseResult.ParseError, R>
230
+ >(2, (persisted, field) =>
231
+ Effect.map(
232
+ Schema.decodeUnknown(
233
+ Schema.Struct({
234
+ [field]: fromJson
235
+ }),
236
+ options
237
+ )(persisted),
238
+ (_) => _[field]
239
+ ))
240
+ }
241
+
242
+ /**
243
+ * @since 1.0.0
244
+ * @category Config
245
+ */
246
+ export const makeConfig = (
247
+ headers: Record<string, string>
248
+ ): Effect.Effect<MP.BaseConfig> =>
249
+ Effect.withFiberRuntime((fiber) => {
250
+ const mimeTypes = Context.get(fiber.currentContext, FieldMimeTypes)
251
+ return Effect.succeed<MP.BaseConfig>({
252
+ headers,
253
+ maxParts: Option.getOrUndefined(Context.get(fiber.currentContext, MaxParts)),
254
+ maxFieldSize: Number(Context.get(fiber.currentContext, MaxFieldSize)),
255
+ maxPartSize: Context.get(fiber.currentContext, MaxFileSize).pipe(Option.map(Number), Option.getOrUndefined),
256
+ maxTotalSize: Context.get(fiber.currentContext, IncomingMessage.MaxBodySize).pipe(
257
+ Option.map(Number),
258
+ Option.getOrUndefined
259
+ ),
260
+ isFile: mimeTypes.length === 0 ? undefined : (info: MP.PartInfo): boolean =>
261
+ !Chunk.some(
262
+ mimeTypes,
263
+ (_) => info.contentType.includes(_)
264
+ ) && MP.defaultIsFile(info)
265
+ })
266
+ })
267
+
268
+ /**
269
+ * @since 1.0.0
270
+ * @category Parsers
271
+ */
272
+ export const makeChannel = <IE>(
273
+ headers: Record<string, string>,
274
+ bufferSize = 16
275
+ ): Channel.Channel<
276
+ Chunk.Chunk<Part>,
277
+ Chunk.Chunk<Uint8Array>,
278
+ MultipartError | IE,
279
+ IE,
280
+ unknown,
281
+ unknown
282
+ > =>
283
+ Channel.acquireUseRelease(
284
+ Effect.all([
285
+ makeConfig(headers),
286
+ Mailbox.make<Chunk.Chunk<Uint8Array>>(bufferSize)
287
+ ]),
288
+ ([config, mailbox]) => {
289
+ let partsBuffer: Array<Part> = []
290
+ let exit = Option.none<Exit.Exit<void, IE | MultipartError>>()
291
+
292
+ const input: AsyncInput.AsyncInputProducer<IE, Chunk.Chunk<Uint8Array>, unknown> = {
293
+ awaitRead: () => Effect.void,
294
+ emit(element) {
295
+ return mailbox.offer(element)
296
+ },
297
+ error(cause) {
298
+ exit = Option.some(Exit.failCause(cause))
299
+ return mailbox.end
300
+ },
301
+ done(_value) {
302
+ return mailbox.end
303
+ }
304
+ }
305
+
306
+ const parser = MP.make({
307
+ ...config,
308
+ onField(info, value) {
309
+ partsBuffer.push(new FieldImpl(info.name, info.contentType, MP.decodeField(info, value)))
310
+ },
311
+ onFile(info) {
312
+ let chunks: Array<Uint8Array> = []
313
+ let finished = false
314
+ const take: Channel.Channel<Chunk.Chunk<Uint8Array>> = Channel.suspend(() => {
315
+ if (chunks.length === 0) {
316
+ return finished ? Channel.void : Channel.zipRight(pump, take)
317
+ }
318
+ const chunk = Chunk.unsafeFromArray(chunks)
319
+ chunks = []
320
+ return finished ? Channel.write(chunk) : Channel.zipRight(
321
+ Channel.write(chunk),
322
+ Channel.zipRight(pump, take)
323
+ )
324
+ })
325
+ partsBuffer.push(new FileImpl(info, take))
326
+ return function(chunk) {
327
+ if (chunk === null) {
328
+ finished = true
329
+ } else {
330
+ chunks.push(chunk)
331
+ }
332
+ }
333
+ },
334
+ onError(error_) {
335
+ exit = Option.some(Exit.fail(convertError(error_)))
336
+ },
337
+ onDone() {
338
+ exit = Option.some(Exit.void)
339
+ }
340
+ })
341
+
342
+ const pump = Channel.flatMap(
343
+ mailbox.takeAll,
344
+ ([chunks, done]) =>
345
+ Channel.sync(() => {
346
+ Chunk.forEach(chunks, Chunk.forEach(parser.write))
347
+ if (done) {
348
+ parser.end()
349
+ }
350
+ })
351
+ )
352
+
353
+ const partsChannel: Channel.Channel<
354
+ Chunk.Chunk<Part>,
355
+ unknown,
356
+ IE | MultipartError
357
+ > = Channel.flatMap(
358
+ pump,
359
+ () => {
360
+ if (partsBuffer.length === 0) {
361
+ return exit._tag === "None" ? partsChannel : writeExit(exit.value)
362
+ }
363
+ const chunk = Chunk.unsafeFromArray(partsBuffer)
364
+ partsBuffer = []
365
+ return Channel.zipRight(
366
+ Channel.write(chunk),
367
+ exit._tag === "None" ? partsChannel : writeExit(exit.value)
368
+ )
369
+ }
370
+ )
371
+
372
+ return Channel.embedInput(partsChannel, input)
373
+ },
374
+ ([, mailbox]) => mailbox.shutdown
375
+ )
376
+
377
+ const writeExit = <A, E>(
378
+ self: Exit.Exit<A, E>
379
+ ): Channel.Channel<never, unknown, E> => self._tag === "Success" ? Channel.void : Channel.failCause(self.cause)
380
+
381
+ function convertError(cause: MP.MultipartError): MultipartError {
382
+ switch (cause._tag) {
383
+ case "ReachedLimit": {
384
+ switch (cause.limit) {
385
+ case "MaxParts": {
386
+ return new MultipartError({ reason: "TooManyParts", cause })
387
+ }
388
+ case "MaxFieldSize": {
389
+ return new MultipartError({ reason: "FieldTooLarge", cause })
390
+ }
391
+ case "MaxPartSize": {
392
+ return new MultipartError({ reason: "FileTooLarge", cause })
393
+ }
394
+ case "MaxTotalSize": {
395
+ return new MultipartError({ reason: "BodyTooLarge", cause })
396
+ }
397
+ }
398
+ }
399
+ default: {
400
+ return new MultipartError({ reason: "Parse", cause })
401
+ }
402
+ }
403
+ }
404
+
405
+ abstract class PartBase extends Inspectable.Class {
406
+ readonly [TypeId]: TypeId
407
+ constructor() {
408
+ super()
409
+ this[TypeId] = TypeId
410
+ }
411
+ }
412
+
413
+ class FieldImpl extends PartBase implements Field {
414
+ readonly _tag = "Field"
415
+
416
+ constructor(
417
+ readonly key: string,
418
+ readonly contentType: string,
419
+ readonly value: string
420
+ ) {
421
+ super()
422
+ }
423
+
424
+ toJSON(): unknown {
425
+ return {
426
+ _id: "@effect/platform/Multipart/Part",
427
+ _tag: "Field",
428
+ key: this.key,
429
+ contentType: this.contentType,
430
+ value: this.value
431
+ }
432
+ }
433
+ }
434
+
435
+ class FileImpl extends PartBase implements File {
436
+ readonly _tag = "File"
437
+ readonly key: string
438
+ readonly name: string
439
+ readonly contentType: string
440
+ readonly content: Stream.Stream<Uint8Array, MultipartError>
441
+ readonly contentEffect: Effect.Effect<Uint8Array, MultipartError>
442
+
443
+ constructor(
444
+ info: MP.PartInfo,
445
+ channel: Channel.Channel<Chunk.Chunk<Uint8Array>, unknown, never, unknown, void, unknown>
446
+ ) {
447
+ super()
448
+ this.key = info.name
449
+ this.name = info.filename ?? info.name
450
+ this.contentType = info.contentType
451
+ this.content = Stream.fromChannel(channel)
452
+ this.contentEffect = channel.pipe(
453
+ Channel.pipeTo(collectUint8Array),
454
+ Channel.run,
455
+ Effect.mapError((cause) => new MultipartError({ reason: "InternalError", cause }))
456
+ )
457
+ }
458
+
459
+ toJSON(): unknown {
460
+ return {
461
+ _id: "@effect/platform/Multipart/Part",
462
+ _tag: "File",
463
+ key: this.key,
464
+ name: this.name,
465
+ contentType: this.contentType
466
+ }
467
+ }
468
+ }
469
+
470
+ const defaultWriteFile = (path: string, file: File) =>
471
+ Effect.flatMap(
472
+ FileSystem.FileSystem,
473
+ (fs) =>
474
+ Effect.mapError(
475
+ Stream.run(file.content, fs.sink(path)),
476
+ (cause) => new MultipartError({ reason: "InternalError", cause })
477
+ )
478
+ )
479
+
480
+ /**
481
+ * @since 1.0.0
482
+ */
483
+ export const collectUint8Array = Channel.suspend(() => {
484
+ let accumulator = new Uint8Array(0)
485
+ const loop: Channel.Channel<
486
+ never,
487
+ Chunk.Chunk<Uint8Array>,
488
+ unknown,
489
+ unknown,
490
+ Uint8Array
491
+ > = Channel.readWithCause({
492
+ onInput(chunk: Chunk.Chunk<Uint8Array>) {
493
+ for (const element of chunk) {
494
+ const newAccumulator = new Uint8Array(accumulator.length + element.length)
495
+ newAccumulator.set(accumulator, 0)
496
+ newAccumulator.set(element, accumulator.length)
497
+ accumulator = newAccumulator
498
+ }
499
+ return loop
500
+ },
501
+ onFailure: (cause: Cause.Cause<unknown>) => Channel.failCause(cause),
502
+ onDone: () => Channel.succeed(accumulator)
503
+ })
504
+ return loop
505
+ })
506
+
507
+ /**
508
+ * @since 1.0.0
509
+ * @category Conversions
510
+ */
511
+ export const toPersisted = (
512
+ stream: Stream.Stream<Part, MultipartError>,
513
+ writeFile = defaultWriteFile
514
+ ): Effect.Effect<Persisted, MultipartError, FileSystem.FileSystem | Path.Path | Scope.Scope> =>
515
+ Effect.gen(function*() {
516
+ const fs = yield* FileSystem.FileSystem
517
+ const path_ = yield* Path.Path
518
+ const dir = yield* fs.makeTempDirectoryScoped()
519
+ const persisted: Record<string, Array<PersistedFile> | Array<string> | string> = Object.create(null)
520
+ yield* Stream.runForEach(stream, (part) => {
521
+ if (part._tag === "Field") {
522
+ if (!(part.key in persisted)) {
523
+ persisted[part.key] = part.value
524
+ } else if (typeof persisted[part.key] === "string") {
525
+ persisted[part.key] = [persisted[part.key] as string, part.value]
526
+ } else {
527
+ ;(persisted[part.key] as Array<string>).push(part.value)
528
+ }
529
+ return Effect.void
530
+ } else if (part.name === "") {
531
+ return Effect.void
532
+ }
533
+ const file = part
534
+ const path = path_.join(dir, path_.basename(file.name).slice(-128))
535
+ const filePart = new PersistedFileImpl(
536
+ file.key,
537
+ file.name,
538
+ file.contentType,
539
+ path
540
+ )
541
+ if (Array.isArray(persisted[part.key])) {
542
+ ;(persisted[part.key] as Array<PersistedFile>).push(filePart)
543
+ } else {
544
+ persisted[part.key] = [filePart]
545
+ }
546
+ return writeFile(path, file)
547
+ })
548
+ return persisted
549
+ }).pipe(
550
+ Effect.catchTags({
551
+ SystemError: (cause) => Effect.fail(new MultipartError({ reason: "InternalError", cause })),
552
+ BadArgument: (cause) => Effect.fail(new MultipartError({ reason: "InternalError", cause }))
553
+ })
554
+ )
555
+
556
+ class PersistedFileImpl extends PartBase implements PersistedFile {
557
+ readonly _tag = "PersistedFile"
558
+
559
+ constructor(
560
+ readonly key: string,
561
+ readonly name: string,
562
+ readonly contentType: string,
563
+ readonly path: string
564
+ ) {
565
+ super()
566
+ }
567
+
568
+ toJSON(): unknown {
569
+ return {
570
+ _id: "@effect/platform/Multipart/Part",
571
+ _tag: "PersistedFile",
572
+ key: this.key,
573
+ name: this.name,
574
+ contentType: this.contentType,
575
+ path: this.path
576
+ }
577
+ }
578
+ }
579
+
580
+ /**
581
+ * @since 1.0.0
582
+ * @category fiber refs
583
+ */
584
+ export const withLimits: {
585
+ /**
586
+ * @since 1.0.0
587
+ * @category fiber refs
588
+ */
589
+ (
590
+ options: {
591
+ readonly maxParts?: Option.Option<number> | undefined
592
+ readonly maxFieldSize?: FileSystem.SizeInput | undefined
593
+ readonly maxFileSize?: Option.Option<FileSystem.SizeInput> | undefined
594
+ readonly maxTotalSize?: Option.Option<FileSystem.SizeInput> | undefined
595
+ readonly fieldMimeTypes?: ReadonlyArray<string> | undefined
596
+ }
597
+ ): <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
598
+ /**
599
+ * @since 1.0.0
600
+ * @category fiber refs
601
+ */
602
+ <A, E, R>(
603
+ effect: Effect.Effect<A, E, R>,
604
+ options: {
605
+ readonly maxParts?: Option.Option<number> | undefined
606
+ readonly maxFieldSize?: FileSystem.SizeInput | undefined
607
+ readonly maxFileSize?: Option.Option<FileSystem.SizeInput> | undefined
608
+ readonly maxTotalSize?: Option.Option<FileSystem.SizeInput> | undefined
609
+ readonly fieldMimeTypes?: ReadonlyArray<string> | undefined
610
+ }
611
+ ): Effect.Effect<A, E, R>
612
+ } = dual(2, <A, E, R>(
613
+ effect: Effect.Effect<A, E, R>,
614
+ options: {
615
+ readonly maxParts?: Option.Option<number> | undefined
616
+ readonly maxFieldSize?: FileSystem.SizeInput | undefined
617
+ readonly maxFileSize?: Option.Option<FileSystem.SizeInput> | undefined
618
+ readonly maxTotalSize?: Option.Option<FileSystem.SizeInput> | undefined
619
+ readonly fieldMimeTypes?: ReadonlyArray<string> | undefined
620
+ }
621
+ ): Effect.Effect<A, E, R> => Effect.provide(effect, withLimitsContext(options)))
622
+
623
+ const withLimitsContext = (options: {
624
+ readonly maxParts?: Option.Option<number> | undefined
625
+ readonly maxFieldSize?: FileSystem.SizeInput | undefined
626
+ readonly maxFileSize?: Option.Option<FileSystem.SizeInput> | undefined
627
+ readonly maxTotalSize?: Option.Option<FileSystem.SizeInput> | undefined
628
+ readonly fieldMimeTypes?: ReadonlyArray<string> | undefined
629
+ }) => {
630
+ const contextMap = new Map<string, unknown>()
631
+ if (options.maxParts !== undefined) {
632
+ contextMap.set(MaxParts.key, options.maxParts)
633
+ }
634
+ if (options.maxFieldSize !== undefined) {
635
+ contextMap.set(MaxFieldSize.key, FileSystem.Size(options.maxFieldSize))
636
+ }
637
+ if (options.maxFileSize !== undefined) {
638
+ contextMap.set(MaxFileSize.key, Option.map(options.maxFileSize, FileSystem.Size))
639
+ }
640
+ if (options.maxTotalSize !== undefined) {
641
+ contextMap.set(IncomingMessage.MaxBodySize.key, Option.map(options.maxTotalSize, FileSystem.Size))
642
+ }
643
+ if (options.fieldMimeTypes !== undefined) {
644
+ contextMap.set(FieldMimeTypes.key, Chunk.fromIterable(options.fieldMimeTypes))
645
+ }
646
+ return Context.unsafeMake(contextMap)
647
+ }
648
+
649
+ /**
650
+ * @since 1.0.0
651
+ * @category fiber refs
652
+ */
653
+ export const withLimitsStream: {
654
+ /**
655
+ * @since 1.0.0
656
+ * @category fiber refs
657
+ */
658
+ (
659
+ options: {
660
+ readonly maxParts?: Option.Option<number> | undefined
661
+ readonly maxFieldSize?: FileSystem.SizeInput | undefined
662
+ readonly maxFileSize?: Option.Option<FileSystem.SizeInput> | undefined
663
+ readonly maxTotalSize?: Option.Option<FileSystem.SizeInput> | undefined
664
+ readonly fieldMimeTypes?: ReadonlyArray<string> | undefined
665
+ }
666
+ ): <A, E, R>(stream: Stream.Stream<A, E, R>) => Stream.Stream<A, E, R>
667
+ /**
668
+ * @since 1.0.0
669
+ * @category fiber refs
670
+ */
671
+ <A, E, R>(
672
+ stream: Stream.Stream<A, E, R>,
673
+ options: {
674
+ readonly maxParts?: Option.Option<number> | undefined
675
+ readonly maxFieldSize?: FileSystem.SizeInput | undefined
676
+ readonly maxFileSize?: Option.Option<FileSystem.SizeInput> | undefined
677
+ readonly maxTotalSize?: Option.Option<FileSystem.SizeInput> | undefined
678
+ readonly fieldMimeTypes?: ReadonlyArray<string> | undefined
679
+ }
680
+ ): Stream.Stream<A, E, R>
681
+ } = dual(2, <A, E, R>(
682
+ stream: Stream.Stream<A, E, R>,
150
683
  options: {
151
- readonly reason: MultipartError["reason"]
152
- readonly cause: unknown
684
+ readonly maxParts?: Option.Option<number> | undefined
685
+ readonly maxFieldSize?: FileSystem.SizeInput | undefined
686
+ readonly maxFileSize?: Option.Option<FileSystem.SizeInput> | undefined
687
+ readonly maxTotalSize?: Option.Option<FileSystem.SizeInput> | undefined
688
+ readonly fieldMimeTypes?: ReadonlyArray<string> | undefined
153
689
  }
154
- ) => MultipartError = internal.MultipartError
690
+ ): Stream.Stream<A, E, R> => Stream.provideSomeContext(stream, withLimitsContext(options)))
155
691
 
156
692
  /**
157
693
  * @since 1.0.0
158
694
  * @category fiber refs
159
695
  */
160
- export const maxParts: FiberRef.FiberRef<Option.Option<number>> = internal.maxParts
696
+ export declare namespace withLimits {
697
+ /**
698
+ * @since 1.0.0
699
+ * @category fiber refs
700
+ */
701
+ export type Options = {
702
+ readonly maxParts?: Option.Option<number> | undefined
703
+ readonly maxFieldSize?: FileSystem.SizeInput | undefined
704
+ readonly maxFileSize?: Option.Option<FileSystem.SizeInput> | undefined
705
+ readonly maxTotalSize?: Option.Option<FileSystem.SizeInput> | undefined
706
+ readonly fieldMimeTypes?: ReadonlyArray<string> | undefined
707
+ }
708
+ }
709
+
710
+ /**
711
+ * @since 1.0.0
712
+ * @category fiber refs
713
+ */
714
+ export class MaxParts extends Context.Reference<MaxParts>()("@effect/platform/Multipart/MaxParts", {
715
+ defaultValue: Option.none<number>
716
+ }) {}
161
717
 
162
718
  /**
163
719
  * @since 1.0.0
@@ -174,13 +730,19 @@ export const withMaxParts: {
174
730
  * @category fiber refs
175
731
  */
176
732
  <A, E, R>(effect: Effect.Effect<A, E, R>, count: Option.Option<number>): Effect.Effect<A, E, R>
177
- } = internal.withMaxParts
733
+ } = dual(
734
+ 2,
735
+ <A, E, R>(effect: Effect.Effect<A, E, R>, count: Option.Option<number>): Effect.Effect<A, E, R> =>
736
+ Effect.provideService(effect, MaxParts, count)
737
+ )
178
738
 
179
739
  /**
180
740
  * @since 1.0.0
181
741
  * @category fiber refs
182
742
  */
183
- export const maxFieldSize: FiberRef.FiberRef<FileSystem.Size> = internal.maxFieldSize
743
+ export class MaxFieldSize extends Context.Reference<MaxFieldSize>()("@effect/platform/Multipart/MaxFieldSize", {
744
+ defaultValue: constant(FileSystem.Size(10 * 1024 * 1024))
745
+ }) {}
184
746
 
185
747
  /**
186
748
  * @since 1.0.0
@@ -197,13 +759,19 @@ export const withMaxFieldSize: {
197
759
  * @category fiber refs
198
760
  */
199
761
  <A, E, R>(effect: Effect.Effect<A, E, R>, size: FileSystem.SizeInput): Effect.Effect<A, E, R>
200
- } = internal.withMaxFieldSize
762
+ } = dual(
763
+ 2,
764
+ <A, E, R>(effect: Effect.Effect<A, E, R>, size: FileSystem.SizeInput): Effect.Effect<A, E, R> =>
765
+ Effect.provideService(effect, MaxFieldSize, FileSystem.Size(size))
766
+ )
201
767
 
202
768
  /**
203
769
  * @since 1.0.0
204
770
  * @category fiber refs
205
771
  */
206
- export const maxFileSize: FiberRef.FiberRef<Option.Option<FileSystem.Size>> = internal.maxFileSize
772
+ export class MaxFileSize extends Context.Reference<MaxFileSize>()("@effect/platform/Multipart/MaxFileSize", {
773
+ defaultValue: Option.none<FileSystem.Size>
774
+ }) {}
207
775
 
208
776
  /**
209
777
  * @since 1.0.0
@@ -220,13 +788,23 @@ export const withMaxFileSize: {
220
788
  * @category fiber refs
221
789
  */
222
790
  <A, E, R>(effect: Effect.Effect<A, E, R>, size: Option.Option<FileSystem.SizeInput>): Effect.Effect<A, E, R>
223
- } = internal.withMaxFileSize
791
+ } = dual(
792
+ 2,
793
+ <A, E, R>(effect: Effect.Effect<A, E, R>, size: Option.Option<FileSystem.SizeInput>): Effect.Effect<A, E, R> =>
794
+ Effect.provideService(
795
+ effect,
796
+ MaxFileSize,
797
+ Option.map(size, FileSystem.Size)
798
+ )
799
+ )
224
800
 
225
801
  /**
226
802
  * @since 1.0.0
227
803
  * @category fiber refs
228
804
  */
229
- export const fieldMimeTypes: FiberRef.FiberRef<Chunk.Chunk<string>> = internal.fieldMimeTypes
805
+ export class FieldMimeTypes extends Context.Reference<FieldMimeTypes>()("@effect/platform/Multipart/FieldMimeTypes", {
806
+ defaultValue: constant<Chunk.Chunk<string>>(Chunk.make("application/json"))
807
+ }) {}
230
808
 
231
809
  /**
232
810
  * @since 1.0.0
@@ -243,82 +821,8 @@ export const withFieldMimeTypes: {
243
821
  * @category fiber refs
244
822
  */
245
823
  <A, E, R>(effect: Effect.Effect<A, E, R>, mimeTypes: ReadonlyArray<string>): Effect.Effect<A, E, R>
246
- } = internal.withFieldMimeTypes
247
-
248
- /**
249
- * @since 1.0.0
250
- * @category schema
251
- */
252
- export const FileSchema: Schema.Schema<PersistedFile> = internal.FileSchema
253
-
254
- /**
255
- * @since 1.0.0
256
- * @category schema
257
- */
258
- export const FilesSchema: Schema.Schema<ReadonlyArray<PersistedFile>> = internal.FilesSchema
259
-
260
- /**
261
- * @since 1.0.0
262
- * @category schema
263
- */
264
- export const SingleFileSchema: Schema.transform<
265
- Schema.Schema<ReadonlyArray<PersistedFile>>,
266
- Schema.Schema<PersistedFile>
267
- > = internal.SingleFileSchema
268
-
269
- /**
270
- * @since 1.0.0
271
- * @category schema
272
- */
273
- export const schemaJson: <A, I, R>(
274
- schema: Schema.Schema<A, I, R>,
275
- options?: ParseOptions | undefined
276
- ) => {
277
- (field: string): (persisted: Persisted) => Effect.Effect<A, ParseResult.ParseError, R>
278
- (persisted: Persisted, field: string): Effect.Effect<A, ParseResult.ParseError, R>
279
- } = internal.schemaJson
280
-
281
- /**
282
- * @since 1.0.0
283
- * @category schema
284
- */
285
- export const schemaPersisted: <A, I extends Partial<Persisted>, R>(
286
- schema: Schema.Schema<A, I, R>,
287
- options?: ParseOptions | undefined
288
- ) => (persisted: Persisted) => Effect.Effect<A, ParseResult.ParseError, R> = internal.schemaPersisted
289
-
290
- /**
291
- * @since 1.0.0
292
- * @category constructors
293
- */
294
- export const makeChannel: <IE>(
295
- headers: Record<string, string>,
296
- bufferSize?: number
297
- ) => Channel.Channel<Chunk.Chunk<Part>, Chunk.Chunk<Uint8Array>, MultipartError | IE, IE, unknown, unknown> =
298
- internal.makeChannel
299
-
300
- /**
301
- * @since 1.0.0
302
- * @category constructors
303
- */
304
- export const makeConfig: (headers: Record<string, string>) => Effect.Effect<Multipasta.BaseConfig> = internal.makeConfig
305
-
306
- /**
307
- * @since 1.0.0
308
- * @category constructors
309
- */
310
- export const toPersisted: (
311
- stream: Stream.Stream<Part, MultipartError>,
312
- writeFile?: (path: string, file: File) => Effect.Effect<void, MultipartError, FileSystem.FileSystem>
313
- ) => Effect.Effect<Persisted, MultipartError, FileSystem.FileSystem | Path.Path | Scope.Scope> = internal.toPersisted
314
-
315
- /**
316
- * @since 1.0.0
317
- */
318
- export const collectUint8Array: Channel.Channel<
319
- never,
320
- Chunk.Chunk<Uint8Array>,
321
- unknown,
322
- unknown,
323
- Uint8Array
324
- > = internal.collectUint8Array
824
+ } = dual(
825
+ 2,
826
+ <A, E, R>(effect: Effect.Effect<A, E, R>, mimeTypes: ReadonlyArray<string>): Effect.Effect<A, E, R> =>
827
+ Effect.provideService(effect, FieldMimeTypes, Chunk.fromIterable(mimeTypes))
828
+ )