@effect/platform 0.84.11 → 0.85.1

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 (39) hide show
  1. package/README.md +4 -7
  2. package/dist/cjs/HttpApi.js +1 -1
  3. package/dist/cjs/HttpApi.js.map +1 -1
  4. package/dist/cjs/HttpApiBuilder.js +15 -8
  5. package/dist/cjs/HttpApiBuilder.js.map +1 -1
  6. package/dist/cjs/HttpApiEndpoint.js.map +1 -1
  7. package/dist/cjs/HttpApiSchema.js +28 -1
  8. package/dist/cjs/HttpApiSchema.js.map +1 -1
  9. package/dist/cjs/Multipart.js +5 -1
  10. package/dist/cjs/Multipart.js.map +1 -1
  11. package/dist/cjs/internal/multipart.js +24 -1
  12. package/dist/cjs/internal/multipart.js.map +1 -1
  13. package/dist/dts/HttpApi.d.ts.map +1 -1
  14. package/dist/dts/HttpApiBuilder.d.ts +2 -2
  15. package/dist/dts/HttpApiBuilder.d.ts.map +1 -1
  16. package/dist/dts/HttpApiEndpoint.d.ts +26 -6
  17. package/dist/dts/HttpApiEndpoint.d.ts.map +1 -1
  18. package/dist/dts/HttpApiSchema.d.ts +31 -0
  19. package/dist/dts/HttpApiSchema.d.ts.map +1 -1
  20. package/dist/dts/Multipart.d.ts +5 -0
  21. package/dist/dts/Multipart.d.ts.map +1 -1
  22. package/dist/esm/HttpApi.js +1 -1
  23. package/dist/esm/HttpApi.js.map +1 -1
  24. package/dist/esm/HttpApiBuilder.js +16 -9
  25. package/dist/esm/HttpApiBuilder.js.map +1 -1
  26. package/dist/esm/HttpApiEndpoint.js.map +1 -1
  27. package/dist/esm/HttpApiSchema.js +25 -0
  28. package/dist/esm/HttpApiSchema.js.map +1 -1
  29. package/dist/esm/Multipart.js +4 -0
  30. package/dist/esm/Multipart.js.map +1 -1
  31. package/dist/esm/internal/multipart.js +23 -0
  32. package/dist/esm/internal/multipart.js.map +1 -1
  33. package/package.json +2 -2
  34. package/src/HttpApi.ts +3 -1
  35. package/src/HttpApiBuilder.ts +19 -13
  36. package/src/HttpApiEndpoint.ts +38 -7
  37. package/src/HttpApiSchema.ts +51 -0
  38. package/src/Multipart.ts +12 -0
  39. package/src/internal/multipart.ts +32 -0
@@ -7,7 +7,7 @@ import * as Context from "effect/Context"
7
7
  import * as Effect from "effect/Effect"
8
8
  import * as Encoding from "effect/Encoding"
9
9
  import * as Fiber from "effect/Fiber"
10
- import { identity } from "effect/Function"
10
+ import { constFalse, identity } from "effect/Function"
11
11
  import { globalValue } from "effect/GlobalValue"
12
12
  import * as Layer from "effect/Layer"
13
13
  import * as ManagedRuntime from "effect/ManagedRuntime"
@@ -242,7 +242,7 @@ export interface Handlers<
242
242
  */
243
243
  handleRaw<Name extends HttpApiEndpoint.HttpApiEndpoint.Name<Endpoints>, R1>(
244
244
  name: Name,
245
- handler: HttpApiEndpoint.HttpApiEndpoint.HandlerResponseWithName<Endpoints, Name, E, R1>,
245
+ handler: HttpApiEndpoint.HttpApiEndpoint.HandlerRawWithName<Endpoints, Name, E, R1>,
246
246
  options?: { readonly uninterruptible?: boolean | undefined } | undefined
247
247
  ): Handlers<
248
248
  E,
@@ -286,7 +286,7 @@ export declare namespace Handlers {
286
286
  export type Item<E, R> = {
287
287
  readonly endpoint: HttpApiEndpoint.HttpApiEndpoint.Any
288
288
  readonly handler: HttpApiEndpoint.HttpApiEndpoint.Handler<any, E, R>
289
- readonly withFullResponse: boolean
289
+ readonly withFullRequest: boolean
290
290
  readonly uninterruptible: boolean
291
291
  }
292
292
 
@@ -390,7 +390,7 @@ const HandlersProto = {
390
390
  handlers: Chunk.append(this.handlers, {
391
391
  endpoint,
392
392
  handler,
393
- withFullResponse: false,
393
+ withFullRequest: false,
394
394
  uninterruptible: options?.uninterruptible ?? false
395
395
  }) as any
396
396
  })
@@ -407,7 +407,7 @@ const HandlersProto = {
407
407
  handlers: Chunk.append(this.handlers, {
408
408
  endpoint,
409
409
  handler,
410
- withFullResponse: true,
410
+ withFullRequest: true,
411
411
  uninterruptible: options?.uninterruptible ?? false
412
412
  }) as any
413
413
  })
@@ -480,7 +480,7 @@ export const group = <
480
480
  (input) => Context.merge(context, input)
481
481
  )
482
482
  },
483
- item.withFullResponse,
483
+ item.withFullRequest,
484
484
  item.uninterruptible
485
485
  ))
486
486
  }
@@ -623,12 +623,18 @@ const handlerToRoute = (
623
623
  endpoint_: HttpApiEndpoint.HttpApiEndpoint.Any,
624
624
  middleware: MiddlewareMap,
625
625
  handler: HttpApiEndpoint.HttpApiEndpoint.Handler<any, any, any>,
626
- isFullResponse: boolean,
626
+ isFullRequest: boolean,
627
627
  uninterruptible: boolean
628
628
  ): HttpRouter.Route<any, any> => {
629
629
  const endpoint = endpoint_ as HttpApiEndpoint.HttpApiEndpoint.AnyWithProps
630
+ const isMultipartStream = endpoint.payloadSchema.pipe(
631
+ Option.map(({ ast }) => HttpApiSchema.getMultipartStream(ast)),
632
+ Option.getOrElse(constFalse)
633
+ )
630
634
  const decodePath = Option.map(endpoint.pathSchema, Schema.decodeUnknown)
631
- const decodePayload = Option.map(endpoint.payloadSchema, Schema.decodeUnknown)
635
+ const decodePayload = isFullRequest || isMultipartStream
636
+ ? Option.none()
637
+ : Option.map(endpoint.payloadSchema, Schema.decodeUnknown)
632
638
  const decodeHeaders = Option.map(endpoint.headersSchema, Schema.decodeUnknown)
633
639
  const encodeSuccess = Schema.encode(makeSuccessSchema(endpoint.successSchema))
634
640
  return HttpRouter.makeRoute(
@@ -642,7 +648,7 @@ const handlerToRoute = (
642
648
  const httpRequest = Context.unsafeGet(context, HttpServerRequest.HttpServerRequest)
643
649
  const routeContext = Context.unsafeGet(context, HttpRouter.RouteContext)
644
650
  const urlParams = Context.unsafeGet(context, HttpServerRequest.ParsedSearchParams)
645
- const request: any = {}
651
+ const request: any = { request: httpRequest }
646
652
  if (decodePath._tag === "Some") {
647
653
  request.path = yield* decodePath.value(routeContext.params)
648
654
  }
@@ -651,6 +657,8 @@ const handlerToRoute = (
651
657
  requestPayload(httpRequest, urlParams),
652
658
  decodePayload.value
653
659
  )
660
+ } else if (isMultipartStream) {
661
+ request.payload = httpRequest.multipartStream
654
662
  }
655
663
  if (decodeHeaders._tag === "Some") {
656
664
  request.headers = yield* decodeHeaders.value(httpRequest.headers)
@@ -659,10 +667,8 @@ const handlerToRoute = (
659
667
  const schema = endpoint.urlParamsSchema.value
660
668
  request.urlParams = yield* Schema.decodeUnknown(schema)(normalizeUrlParams(urlParams, schema.ast))
661
669
  }
662
- const response = isFullResponse
663
- ? yield* handler(request)
664
- : yield* Effect.flatMap(handler(request), encodeSuccess)
665
- return response as HttpServerResponse.HttpServerResponse
670
+ const response = yield* handler(request)
671
+ return HttpServerResponse.isServerResponse(response) ? response : yield* encodeSuccess(response)
666
672
  }).pipe(
667
673
  Effect.catchIf(ParseResult.isParseError, HttpApiDecodeError.refailParseError)
668
674
  )
@@ -8,12 +8,15 @@ import * as Option from "effect/Option"
8
8
  import { type Pipeable, pipeArguments } from "effect/Pipeable"
9
9
  import * as Predicate from "effect/Predicate"
10
10
  import * as Schema from "effect/Schema"
11
+ import type * as Stream from "effect/Stream"
11
12
  import type * as Types from "effect/Types"
12
13
  import type * as HttpApiMiddleware from "./HttpApiMiddleware.js"
13
14
  import * as HttpApiSchema from "./HttpApiSchema.js"
14
15
  import type { HttpMethod } from "./HttpMethod.js"
15
16
  import * as HttpRouter from "./HttpRouter.js"
17
+ import type { HttpServerRequest } from "./HttpServerRequest.js"
16
18
  import type { HttpServerResponse } from "./HttpServerResponse.js"
19
+ import type * as Multipart from "./Multipart.js"
17
20
 
18
21
  /**
19
22
  * @since 1.0.0
@@ -402,8 +405,34 @@ export declare namespace HttpApiEndpoint {
402
405
  > ?
403
406
  & ([_Path] extends [never] ? {} : { readonly path: _Path })
404
407
  & ([_UrlParams] extends [never] ? {} : { readonly urlParams: _UrlParams })
405
- & ([_Payload] extends [never] ? {} : { readonly payload: _Payload })
408
+ & ([_Payload] extends [never] ? {}
409
+ : _Payload extends Brand<HttpApiSchema.MultipartStreamTypeId> ?
410
+ { readonly payload: Stream.Stream<Multipart.Part, Multipart.MultipartError> }
411
+ : { readonly payload: _Payload })
406
412
  & ([_Headers] extends [never] ? {} : { readonly headers: _Headers })
413
+ & { readonly request: HttpServerRequest }
414
+ : {}
415
+
416
+ /**
417
+ * @since 1.0.0
418
+ * @category models
419
+ */
420
+ export type RequestRaw<Endpoint extends Any> = Endpoint extends HttpApiEndpoint<
421
+ infer _Name,
422
+ infer _Method,
423
+ infer _Path,
424
+ infer _UrlParams,
425
+ infer _Payload,
426
+ infer _Headers,
427
+ infer _Success,
428
+ infer _Error,
429
+ infer _R,
430
+ infer _RE
431
+ > ?
432
+ & ([_Path] extends [never] ? {} : { readonly path: _Path })
433
+ & ([_UrlParams] extends [never] ? {} : { readonly urlParams: _UrlParams })
434
+ & ([_Headers] extends [never] ? {} : { readonly headers: _Headers })
435
+ & { readonly request: HttpServerRequest }
407
436
  : {}
408
437
 
409
438
  /**
@@ -416,7 +445,9 @@ export declare namespace HttpApiEndpoint {
416
445
  & ([Headers] extends [never] ? {} : { readonly headers: Headers })
417
446
  & ([Payload] extends [never] ? {}
418
447
  : Payload extends infer P ?
419
- P extends Brand<HttpApiSchema.MultipartTypeId> ? { readonly payload: FormData } : { readonly payload: P }
448
+ P extends Brand<HttpApiSchema.MultipartTypeId> | Brand<HttpApiSchema.MultipartStreamTypeId>
449
+ ? { readonly payload: FormData }
450
+ : { readonly payload: P }
420
451
  : { readonly payload: Payload })
421
452
  ) extends infer Req ? keyof Req extends never ? (void | { readonly withResponse?: WithResponse }) :
422
453
  Req & { readonly withResponse?: WithResponse } :
@@ -464,15 +495,15 @@ export declare namespace HttpApiEndpoint {
464
495
  */
465
496
  export type Handler<Endpoint extends Any, E, R> = (
466
497
  request: Types.Simplify<Request<Endpoint>>
467
- ) => Effect<Success<Endpoint>, Error<Endpoint> | E, R>
498
+ ) => Effect<Success<Endpoint> | HttpServerResponse, Error<Endpoint> | E, R>
468
499
 
469
500
  /**
470
501
  * @since 1.0.0
471
502
  * @category models
472
503
  */
473
- export type HandlerResponse<Endpoint extends Any, E, R> = (
474
- request: Types.Simplify<Request<Endpoint>>
475
- ) => Effect<HttpServerResponse, Error<Endpoint> | E, R>
504
+ export type HandlerRaw<Endpoint extends Any, E, R> = (
505
+ request: Types.Simplify<RequestRaw<Endpoint>>
506
+ ) => Effect<Success<Endpoint> | HttpServerResponse, Error<Endpoint> | E, R>
476
507
 
477
508
  /**
478
509
  * @since 1.0.0
@@ -500,7 +531,7 @@ export declare namespace HttpApiEndpoint {
500
531
  * @since 1.0.0
501
532
  * @category models
502
533
  */
503
- export type HandlerResponseWithName<Endpoints extends Any, Name extends string, E, R> = HandlerResponse<
534
+ export type HandlerRawWithName<Endpoints extends Any, Name extends string, E, R> = HandlerRaw<
504
535
  WithName<Endpoints, Name>,
505
536
  E,
506
537
  R
@@ -20,6 +20,14 @@ export const AnnotationMultipart: unique symbol = Symbol.for(
20
20
  "@effect/platform/HttpApiSchema/AnnotationMultipart"
21
21
  )
22
22
 
23
+ /**
24
+ * @since 1.0.0
25
+ * @category annotations
26
+ */
27
+ export const AnnotationMultipartStream: unique symbol = Symbol.for(
28
+ "@effect/platform/HttpApiSchema/AnnotationMultipartStream"
29
+ )
30
+
23
31
  /**
24
32
  * @since 1.0.0
25
33
  * @category annotations
@@ -69,6 +77,9 @@ export const extractAnnotations = (ast: AST.Annotations): AST.Annotations => {
69
77
  if (AnnotationMultipart in ast) {
70
78
  result[AnnotationMultipart] = ast[AnnotationMultipart]
71
79
  }
80
+ if (AnnotationMultipartStream in ast) {
81
+ result[AnnotationMultipartStream] = ast[AnnotationMultipartStream]
82
+ }
72
83
  return result
73
84
  }
74
85
 
@@ -102,6 +113,13 @@ export const getEmptyDecodeable = (ast: AST.AST): boolean =>
102
113
  */
103
114
  export const getMultipart = (ast: AST.AST): boolean => getAnnotation<boolean>(ast, AnnotationMultipart) ?? false
104
115
 
116
+ /**
117
+ * @since 1.0.0
118
+ * @category annotations
119
+ */
120
+ export const getMultipartStream = (ast: AST.AST): boolean =>
121
+ getAnnotation<boolean>(ast, AnnotationMultipartStream) ?? false
122
+
105
123
  const encodingJson: Encoding = {
106
124
  kind: "Json",
107
125
  contentType: "application/json"
@@ -411,6 +429,39 @@ export const Multipart = <S extends Schema.Schema.Any>(self: S): Multipart<S> =>
411
429
  [AnnotationMultipart]: true
412
430
  }) as any
413
431
 
432
+ /**
433
+ * @since 1.0.0
434
+ * @category multipart
435
+ */
436
+ export const MultipartStreamTypeId: unique symbol = Symbol.for("@effect/platform/HttpApiSchema/MultipartStream")
437
+
438
+ /**
439
+ * @since 1.0.0
440
+ * @category multipart
441
+ */
442
+ export type MultipartStreamTypeId = typeof MultipartStreamTypeId
443
+
444
+ /**
445
+ * @since 1.0.0
446
+ * @category multipart
447
+ */
448
+ export interface MultipartStream<S extends Schema.Schema.Any> extends
449
+ Schema.Schema<
450
+ Schema.Schema.Type<S> & Brand<MultipartStreamTypeId>,
451
+ Schema.Schema.Encoded<S>,
452
+ Schema.Schema.Context<S>
453
+ >
454
+ {}
455
+
456
+ /**
457
+ * @since 1.0.0
458
+ * @category multipart
459
+ */
460
+ export const MultipartStream = <S extends Schema.Schema.Any>(self: S): MultipartStream<S> =>
461
+ self.annotations({
462
+ [AnnotationMultipartStream]: true
463
+ }) as any
464
+
414
465
  const defaultContentType = (encoding: Encoding["kind"]) => {
415
466
  switch (encoding) {
416
467
  case "Json": {
package/src/Multipart.ts CHANGED
@@ -83,6 +83,7 @@ export interface File extends Part.Proto {
83
83
  readonly name: string
84
84
  readonly contentType: string
85
85
  readonly content: Stream.Stream<Uint8Array, MultipartError>
86
+ readonly contentEffect: Effect.Effect<Uint8Array, MultipartError>
86
87
  }
87
88
 
88
89
  /**
@@ -310,3 +311,14 @@ export const toPersisted: (
310
311
  stream: Stream.Stream<Part, MultipartError>,
311
312
  writeFile?: (path: string, file: File) => Effect.Effect<void, MultipartError, FileSystem.FileSystem>
312
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
@@ -1,3 +1,4 @@
1
+ import type { Cause } from "effect/Cause"
1
2
  import * as Channel from "effect/Channel"
2
3
  import * as Chunk from "effect/Chunk"
3
4
  import * as Effect from "effect/Effect"
@@ -355,6 +356,7 @@ class FileImpl extends PartBase implements Multipart.File {
355
356
  readonly name: string
356
357
  readonly contentType: string
357
358
  readonly content: Stream.Stream<Uint8Array, Multipart.MultipartError>
359
+ readonly contentEffect: Effect.Effect<Uint8Array, Multipart.MultipartError>
358
360
 
359
361
  constructor(
360
362
  info: MP.PartInfo,
@@ -365,6 +367,11 @@ class FileImpl extends PartBase implements Multipart.File {
365
367
  this.name = info.filename ?? info.name
366
368
  this.contentType = info.contentType
367
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
+ )
368
375
  }
369
376
 
370
377
  toJSON(): unknown {
@@ -388,6 +395,31 @@ const defaultWriteFile = (path: string, file: Multipart.File) =>
388
395
  )
389
396
  )
390
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
+
391
423
  /** @internal */
392
424
  export const toPersisted = (
393
425
  stream: Stream.Stream<Multipart.Part, Multipart.MultipartError>,