1/**
2 * Defines endpoint declarations used inside an HTTP API group.
3 *
4 * An endpoint records a stable identifier, HTTP method, router path, request schemas,
5 * response schemas, declared errors, middleware, and annotations. Endpoint
6 * values are declarations, not handlers: builders use them to decode requests,
7 * type handler input, encode responses, generate OpenAPI metadata, and derive
8 * generated-client call signatures. This module also includes HTTP method
9 * constructors, payload and response schema helpers, and type utilities used by
10 * builders and generated clients.
11 *
12 * @stability unstable
13 * @since 4.0.0
14 */
15import * as Arr from "../Array.ts"
16import type { Brand } from "../Brand.ts"
17import * as Context from "../Context.ts"
18import type { Effect } from "../Effect.ts"
19import type * as Sse from "../encoding/Sse.ts"
20import { identity, memoize } from "../Function.ts"
21import type { HttpMethod } from "../http/HttpMethod.ts"
22import * as HttpRouter from "../http/HttpRouter.ts"
23import type { HttpServerRequest } from "../http/HttpServerRequest.ts"
24import type { HttpServerResponse } from "../http/HttpServerResponse.ts"
25import type * as Multipart from "../http/Multipart.ts"
26import { type Pipeable, pipeArguments } from "../Pipeable.ts"
27import * as Predicate from "../Predicate.ts"
28import * as Schema from "../Schema.ts"
29import * as AST from "../SchemaAST.ts"
30import type * as Stream from "../Stream.ts"
31import type { Simplify } from "../Struct.ts"
32import type * as Types from "../Types.ts"
33import type * as HttpApiGroup from "./HttpApiGroup.ts"
34import type * as HttpApiMiddleware from "./HttpApiMiddleware.ts"
35import * as HttpApiSchema from "./HttpApiSchema.ts"
36import * as MediaType from "./internal/mediaType.ts"
37
38const TypeId = "~effect/http-api/HttpApiEndpoint"
39
40/**
41 * Returns `true` when a value is an `HttpApiEndpoint`, narrowing the value to the
42 * endpoint interface.
43 *
44 * @stability unstable
45 * @category guards
46 * @since 4.0.0
47 */
48export const isHttpApiEndpoint = (u: unknown): u is Top => Predicate.hasProperty(u, TypeId)
49
50type SuccessType<S> = S extends HttpApiSchema.WithHeaders<
51 infer _Inner,
52 infer _Headers
53> ? HttpApiSchema.withHeaders<SuccessType<_Inner>, _Headers["Type"]>
54 : S extends HttpApiSchema.StreamSse<
55 infer _Events,
56 infer _Error,
57 infer _Value
58 > ? Stream.Stream<_Value, _Error["Type"], never>
59 : S extends HttpApiSchema.StreamUint8Array ? Stream.Stream<Uint8Array, unknown, never>
60 : S extends Schema.Constraint ? S["Type"]
61 : never
62
63type SuccessEncodingServices<S> = S extends HttpApiSchema.StreamSse<
64 infer _Events,
65 infer _Error,
66 infer _Value
67> ? _Events["EncodingServices"] | _Error["EncodingServices"]
68 : S extends HttpApiSchema.StreamUint8Array ? never
69 : S extends Schema.Constraint ? S["EncodingServices"]
70 : never
71
72type SuccessDecodingServices<S> = S extends HttpApiSchema.StreamSse<
73 infer _Events,
74 infer _Error,
75 infer _Value
76> ? _Events["DecodingServices"] | _Error["DecodingServices"]
77 : S extends HttpApiSchema.StreamUint8Array ? never
78 : S extends Schema.Constraint ? S["DecodingServices"]
79 : never
80
81type UnwrapReadonlyArray<S> = S extends ReadonlyArray<infer A> ? A : S
82
83type ExtractBufferedSuccess<S extends SuccessConstraint> = Exclude<
84 Extract<UnwrapReadonlyArray<S>, Schema.Top>,
85 HttpApiSchema.StreamSchema | HttpApiSchema.WithHeaders<Schema.Top, Schema.Top>
86>
87
88type ExtractStreamSuccess<S extends SuccessConstraint> = UnwrapReadonlyArray<S> extends infer Success ?
89 Success extends HttpApiSchema.StreamSchema ? Success : never
90 : never
91
92type ExtractWithHeadersSuccess<S extends SuccessConstraint> = UnwrapReadonlyArray<S> extends infer Success ?
93 Success extends HttpApiSchema.WithHeaders<infer _Inner, infer _Headers> ? HttpApiSchema.WithHeaders<
94 _Inner extends HttpApiSchema.StreamSchema ? _Inner : Schema.toCodecJson<_Inner>,
95 Schema.toCodecStringTree<_Headers>
96 > :
97 never
98 : never
99
100type ToSuccessCodec<S extends SuccessConstraint> = [ExtractBufferedSuccess<S>] extends [never] ?
101 ExtractStreamSuccess<S> | ExtractWithHeadersSuccess<S>
102 : Schema.toCodecJson<ExtractBufferedSuccess<S>> | ExtractStreamSuccess<S> | ExtractWithHeadersSuccess<S>
103
104type ToJsonCodec<S> = [S] extends [never] ? never
105 : [S] extends [Schema.Constraint] ? Schema.toCodecJson<S>
106 : never
107
108type ToStringTreeCodec<S> = [S] extends [never] ? never
109 : [S] extends [Schema.Struct.Fields] ? Schema.toCodecStringTree<Schema.Struct<S>>
110 : [S] extends [Schema.Constraint] ? Schema.toCodecStringTree<S>
111 : never
112
113type RequestFromParts<Endpoint, ParamsType, QueryType, PayloadType, HeadersType> =
114 & ([ParamsType] extends [never] ? {} : { readonly params: Simplify<ParamsType> })
115 & ([QueryType] extends [never] ? {} : { readonly query: Simplify<QueryType> })
116 & ([PayloadType] extends [never] ? {}
117 : PayloadType extends Brand<HttpApiSchema.MultipartStreamTypeId> ?
118 { readonly payload: Stream.Stream<Multipart.Part, Multipart.MultipartError> }
119 : { readonly payload: Simplify<PayloadType> })
120 & ([HeadersType] extends [never] ? {} : { readonly headers: Simplify<HeadersType> })
121 & {
122 readonly request: HttpServerRequest
123 readonly endpoint: Endpoint
124 readonly group: HttpApiGroup.Top
125 }
126
127type RequestRawFromParts<Endpoint, ParamsType, QueryType, HeadersType> =
128 & ([ParamsType] extends [never] ? {} : { readonly params: Simplify<ParamsType> })
129 & ([QueryType] extends [never] ? {} : { readonly query: Simplify<QueryType> })
130 & ([HeadersType] extends [never] ? {} : { readonly headers: Simplify<HeadersType> })
131 & {
132 readonly request: HttpServerRequest
133 readonly endpoint: Endpoint
134 readonly group: HttpApiGroup.Top
135 }
136
137/**
138 * Maps normalized media types to a payload encoding strategy and one or more
139 * schemas. Each schema retains its declared content type in its encoding annotation.
140 *
141 * @stability unstable
142 * @category models
143 * @since 4.0.0
144 */
145export type PayloadMap = ReadonlyMap<string, {
146 readonly encoding: HttpApiSchema.PayloadEncoding
147 readonly schemas: readonly [Schema.Top, ...Array<Schema.Top>]
148}>
149
150/**
151 * Represents an API endpoint. An API endpoint is mapped to a single route on
152 * the underlying `HttpRouter`.
153 *
154 * @stability unstable
155 * @category models
156 * @since 4.0.0
157 */
158export interface HttpApiEndpoint<
159 out Identifier extends string,
160 out Method extends HttpMethod,
161 out Path extends string,
162 out Params extends Schema.Top = never,
163 out Query extends Schema.Top = never,
164 out Payload extends Schema.Top = never,
165 out Headers extends Schema.Top = never,
166 out Success extends Schema.Top = typeof HttpApiSchema.NoContent,
167 out Error extends Schema.Top = never,
168 in out Middleware = never,
169 out MiddlewareServices = never
170> extends Pipeable {
171 new(_: never): {}
172 readonly [TypeId]: typeof TypeId
173 readonly "~Params": Params
174 readonly "~Query": Query
175 readonly "~Headers": Headers
176 readonly "~Payload": Payload
177 readonly "~Success": Success
178 readonly "~Error": Error
179 readonly "~Middleware": Middleware
180 readonly "~MiddlewareServices": MiddlewareServices
181 readonly "~Request": RequestFromParts<this, Params["Type"], Query["Type"], Payload["Type"], Headers["Type"]>
182 readonly "~RequestRaw": RequestRawFromParts<this, Params["Type"], Query["Type"], Headers["Type"]>
183
184 readonly identifier: Identifier
185 readonly path: Path
186 readonly method: Method
187 readonly params: Schema.Top | undefined
188 readonly query: Schema.Top | undefined
189 readonly headers: Schema.Top | undefined
190 readonly payload: PayloadMap
191 readonly success: ReadonlySet<Schema.Top>
192 readonly error: ReadonlySet<Schema.Top>
193 readonly annotations: Context.Context<never>
194 readonly middlewares: ReadonlySet<Context.Key<Middleware, any>>
195 readonly disableCodecs: boolean
196
197 /**
198 * Add a prefix to the path of the endpoint.
199 */
200 prefix<const Prefix extends HttpRouter.PathInput>(
201 prefix: Prefix
202 ): HttpApiEndpoint<
203 Identifier,
204 Method,
205 `${Prefix}${Path}`,
206 Params,
207 Query,
208 Payload,
209 Headers,
210 Success,
211 Error,
212 Middleware,
213 MiddlewareServices
214 >
215
216 /**
217 * Add an `HttpApiMiddleware` to the endpoint.
218 */
219 middleware<I extends HttpApiMiddleware.AnyId, S>(middleware: Context.Key<I, S>): HttpApiEndpoint<
220 Identifier,
221 Method,
222 Path,
223 Params,
224 Query,
225 Payload,
226 Headers,
227 Success,
228 Error,
229 Middleware | I,
230 HttpApiMiddleware.ApplyServices<I, MiddlewareServices>
231 >
232
233 /**
234 * Add an annotation on the endpoint.
235 */
236 annotate<I, S>(
237 key: Context.Key<I, S>,
238 value: Types.NoInfer<S>
239 ): HttpApiEndpoint<
240 Identifier,
241 Method,
242 Path,
243 Params,
244 Query,
245 Payload,
246 Headers,
247 Success,
248 Error,
249 Middleware,
250 MiddlewareServices
251 >
252
253 /**
254 * Merge the annotations of the endpoint with the provided context.
255 */
256 annotateMerge<I>(
257 annotations: Context.Context<I>
258 ): HttpApiEndpoint<
259 Identifier,
260 Method,
261 Path,
262 Params,
263 Query,
264 Payload,
265 Headers,
266 Success,
267 Error,
268 Middleware,
269 MiddlewareServices
270 >
271}
272
273/**
274 * @internal
275 */
276export function getPayloadSchemas(endpoint: Top): Array<Schema.Top> {
277 const result: Array<Schema.Top> = []
278 for (const { schemas } of endpoint.payload.values()) {
279 result.push(...schemas)
280 }
281 return result
282}
283
284/**
285 * @internal
286 */
287export function getSuccessSchemas(endpoint: Top): [Schema.Top, ...Array<Schema.Top>] {
288 const schemas = Array.from(endpoint.success)
289 return Arr.isArrayNonEmpty(schemas) ? schemas : [HttpApiSchema.NoContent]
290}
291
292/**
293 * @internal
294 */
295export function getErrorSchemas(endpoint: Top): Array<Schema.Top> {
296 const schemas = new Set<Schema.Top>(endpoint.error)
297 const transform = endpoint.disableCodecs ? identity : transformResponseSchema
298 for (const middleware of endpoint.middlewares) {
299 const key = middleware as any as HttpApiMiddleware.AnyService
300 for (const schema of key.error) {
301 schemas.add(transform(schema))
302 }
303 }
304 return Array.from(schemas)
305}
306
307/**
308 * A widened `HttpApiEndpoint` type used when the concrete method, path, schemas,
309 * and middleware types are not needed.
310 *
311 * @stability unstable
312 * @category models
313 * @since 4.0.0
314 */
315export interface Constraint {
316 readonly [TypeId]: typeof TypeId
317 readonly identifier: string
318 readonly ["~Success"]: Schema.Constraint
319 readonly ["~Error"]: Schema.Constraint
320 readonly ["~Request"]: unknown
321 readonly ["~RequestRaw"]: unknown
322}
323
324/**
325 * A widened endpoint type that preserves request and middleware pipeline phantom fields.
326 *
327 * @stability unstable
328 * @category models
329 * @since 4.0.0
330 */
331export interface ConstraintRequest extends Constraint {
332 readonly ["~Params"]: Schema.Constraint
333 readonly ["~Query"]: Schema.Constraint
334 readonly ["~Payload"]: Schema.Constraint
335 readonly ["~Headers"]: Schema.Constraint
336 readonly ["~Middleware"]: unknown
337}
338
339/**
340 * A widened endpoint type that preserves concrete runtime properties such as
341 * method, path, schemas, annotations, and middleware sets.
342 *
343 * @stability unstable
344 * @category models
345 * @since 4.0.0
346 */
347export interface Top extends
348 HttpApiEndpoint<
349 string,
350 HttpMethod,
351 string,
352 Schema.Top,
353 Schema.Top,
354 Schema.Top,
355 Schema.Top,
356 Schema.Top,
357 Schema.Top,
358 any,
359 unknown
360 >
361{}
362
363/**
364 * Extracts the endpoint identifier literal from an `HttpApiEndpoint`.
365 *
366 * @stability unstable
367 * @category utility types
368 * @since 4.0.0
369 */
370export type Identifier<Endpoint> = Endpoint extends Constraint ? Endpoint["identifier"] : never
371
372/**
373 * Extracts the success schema associated with an endpoint.
374 *
375 * @stability unstable
376 * @category utility types
377 * @since 4.0.0
378 */
379export type Success<Endpoint> = Endpoint extends Constraint ? Endpoint["~Success"] : never
380
381/**
382 * Extracts the error schema associated with an endpoint.
383 *
384 * @stability unstable
385 * @category utility types
386 * @since 4.0.0
387 */
388export type Error<Endpoint> = Endpoint extends Constraint ? Endpoint["~Error"] : never
389
390/**
391 * Extracts the schema used for an endpoint's path parameters.
392 *
393 * @stability unstable
394 * @category utility types
395 * @since 4.0.0
396 */
397export type Params<Endpoint> = Endpoint extends ConstraintRequest ? Endpoint["~Params"]
398 : never
399
400/**
401 * Extracts the schema used for an endpoint's query parameters.
402 *
403 * @stability unstable
404 * @category utility types
405 * @since 4.0.0
406 */
407export type Query<Endpoint> = Endpoint extends ConstraintRequest ? Endpoint["~Query"]
408 : never
409
410/**
411 * Extracts the schema used for an endpoint's request payload.
412 *
413 * @stability unstable
414 * @category utility types
415 * @since 4.0.0
416 */
417export type Payload<Endpoint> = Endpoint extends ConstraintRequest ? Endpoint["~Payload"]
418 : never
419
420/**
421 * Extracts the schema used for an endpoint's request headers.
422 *
423 * @stability unstable
424 * @category utility types
425 * @since 4.0.0
426 */
427export type Headers<Endpoint> = Endpoint extends ConstraintRequest ? Endpoint["~Headers"]
428 : never
429
430/**
431 * Extracts the middleware identifiers attached to an endpoint.
432 *
433 * @stability unstable
434 * @category utility types
435 * @since 4.0.0
436 */
437export type Middleware<Endpoint> = Endpoint extends { readonly "~Middleware": infer M } ? M
438 : never
439
440/**
441 * Computes the services provided by the middleware attached to an endpoint.
442 *
443 * @stability unstable
444 * @category utility types
445 * @since 4.0.0
446 */
447export type MiddlewareProvides<Endpoint> = HttpApiMiddleware.Provides<Middleware<Endpoint>>
448
449/**
450 * Computes the client-side middleware services required by an endpoint.
451 *
452 * @stability unstable
453 * @category utility types
454 * @since 4.0.0
455 */
456export type MiddlewareClient<Endpoint> = HttpApiMiddleware.MiddlewareClient<Middleware<Endpoint>>
457
458/**
459 * Computes the error types that can be produced by the middleware attached to an
460 * endpoint.
461 *
462 * @stability unstable
463 * @category utility types
464 * @since 4.0.0
465 */
466export type MiddlewareError<Endpoint> = HttpApiMiddleware.Error<Middleware<Endpoint>>
467
468/**
469 * Computes the full error value union for an endpoint, including the endpoint
470 * error schema's type and errors introduced by middleware.
471 *
472 * @stability unstable
473 * @category utility types
474 * @since 4.0.0
475 */
476export type Errors<Endpoint> = Endpoint extends ConstraintRequest ?
477 Endpoint["~Error"]["Type"] | HttpApiMiddleware.Error<Endpoint["~Middleware"]>
478 : never
479
480/**
481 * Computes the services required to encode an endpoint's error responses,
482 * including services required by middleware error encoders.
483 *
484 * @stability unstable
485 * @category utility types
486 * @since 4.0.0
487 */
488export type ErrorServicesEncode<Endpoint> = Endpoint extends ConstraintRequest ?
489 | Endpoint["~Error"]["EncodingServices"]
490 | HttpApiMiddleware.ErrorServicesEncode<Endpoint["~Middleware"]>
491 : never
492
493/**
494 * Builds the decoded request shape passed to a normal endpoint handler, including
495 * available params, query, payload, headers, the raw request, endpoint, and group.
496 * Multipart stream payloads are exposed as streams of parts.
497 *
498 * @stability unstable
499 * @category utility types
500 * @since 4.0.0
501 */
502export type Request<Endpoint> = Endpoint extends ConstraintRequest ? Endpoint["~Request"]
503 : {}
504
505/**
506 * Builds the request shape passed to a raw endpoint handler, including decoded
507 * params, query, and headers plus the raw request, endpoint, and group, while
508 * leaving payload handling to the raw request.
509 *
510 * @stability unstable
511 * @category utility types
512 * @since 4.0.0
513 */
514export type RequestRaw<Endpoint> = Endpoint extends ConstraintRequest ? Endpoint["~RequestRaw"]
515 : {}
516
517/**
518 * Builds the request object accepted by a generated client method, including the
519 * params, query, headers, and payload required by the endpoint, plus optional
520 * response mode and SSE decoding options. Multipart payloads are supplied as
521 * `FormData`.
522 *
523 * @stability unstable
524 * @category utility types
525 * @since 4.0.0
526 */
527export type ClientRequest<
528 Params extends Schema.Constraint,
529 Query extends Schema.Constraint,
530 Payload extends Schema.Constraint,
531 Headers extends Schema.Constraint,
532 ResponseMode extends ClientResponseMode
533> = (
534 & ([Params["Type"]] extends [never] ? {} : { readonly params: Params["Type"] })
535 & ([Query["Type"]] extends [never] ? {} : { readonly query: Query["Type"] })
536 & ([Headers["Type"]] extends [never] ? {} : { readonly headers: Headers["Type"] })
537 & ([Payload["Type"]] extends [never] ? {}
538 : Payload["Type"] extends infer P ?
539 P extends Brand<HttpApiSchema.MultipartTypeId> | Brand<HttpApiSchema.MultipartStreamTypeId>
540 ? { readonly payload: FormData }
541 : { readonly payload: Payload["Type"] }
542 : { readonly payload: Payload["Type"] })
543) extends infer Req ? keyof Req extends never ? (void | {
544 readonly responseMode?: ResponseMode
545 readonly sseOptions?: Sse.DecodeOptions | undefined
546 }) :
547 Req & {
548 readonly responseMode?: ResponseMode
549 readonly sseOptions?: Sse.DecodeOptions | undefined
550 } :
551 void
552
553/**
554 * Controls what a generated client method returns: the decoded success value,
555 * the decoded value paired with the raw response, or only the raw response.
556 *
557 * @stability unstable
558 * @category models
559 * @since 4.0.0
560 */
561export type ClientResponseMode = "decoded-only" | "decoded-and-response" | "response-only"
562
563/**
564 * Computes the services required on the server to decode endpoint inputs and
565 * encode endpoint success, error, and middleware error responses.
566 *
567 * @stability unstable
568 * @category utility types
569 * @since 4.0.0
570 */
571export type ServerServices<Endpoint> = Endpoint extends ConstraintRequest ?
572 | Endpoint["~Params"]["DecodingServices"]
573 | Endpoint["~Query"]["DecodingServices"]
574 | Endpoint["~Payload"]["DecodingServices"]
575 | Endpoint["~Headers"]["DecodingServices"]
576 | SuccessEncodingServices<Endpoint["~Success"]>
577 | Endpoint["~Error"]["EncodingServices"]
578 | HttpApiMiddleware.ErrorServicesEncode<Endpoint["~Middleware"]>
579 : never
580
581/**
582 * Computes the services required on the client to encode endpoint requests and
583 * decode endpoint success or error responses.
584 *
585 * @stability unstable
586 * @category utility types
587 * @since 4.0.0
588 */
589export type ClientServices<Endpoint> = Endpoint extends ConstraintRequest ?
590 | Endpoint["~Params"]["EncodingServices"]
591 | Endpoint["~Query"]["EncodingServices"]
592 | Endpoint["~Payload"]["EncodingServices"]
593 | Endpoint["~Headers"]["EncodingServices"]
594 | SuccessDecodingServices<Endpoint["~Success"]>
595 | Endpoint["~Error"]["DecodingServices"]
596 : never
597
598/**
599 * Extracts the additional services required by middleware applied to an endpoint.
600 *
601 * @stability unstable
602 * @category utility types
603 * @since 4.0.0
604 */
605export type MiddlewareServices<Endpoint> = Endpoint extends { readonly "~MiddlewareServices": infer R } ? R
606 : never
607
608/**
609 * Computes the services required to decode an endpoint's error responses,
610 * including services required by middleware error decoders.
611 *
612 * @stability unstable
613 * @category utility types
614 * @since 4.0.0
615 */
616export type ErrorServicesDecode<Endpoint> = Endpoint extends ConstraintRequest ?
617 | Endpoint["~Error"]["DecodingServices"]
618 | HttpApiMiddleware.ErrorServicesDecode<Endpoint["~Middleware"]>
619 : never
620
621/**
622 * The normal server handler for an endpoint, accepting the decoded request shape
623 * and returning either the endpoint success value or a custom `HttpServerResponse`.
624 *
625 * @stability unstable
626 * @category utility types
627 * @since 4.0.0
628 */
629export type Handler<Endpoint extends Constraint, E, R> = (
630 request: Simplify<Endpoint["~Request"]>
631) => Effect<SuccessType<Endpoint["~Success"]> | HttpServerResponse, Endpoint["~Error"]["Type"] | E, R>
632
633/**
634 * The raw server handler for an endpoint, receiving a request shape without a
635 * decoded payload so the handler can read the raw `HttpServerRequest` directly.
636 *
637 * @stability unstable
638 * @category utility types
639 * @since 4.0.0
640 */
641export type HandlerRaw<Endpoint extends Constraint, E, R> = (
642 request: Simplify<Endpoint["~RequestRaw"]>
643) => Effect<SuccessType<Endpoint["~Success"]> | HttpServerResponse, Endpoint["~Error"]["Type"] | E, R>
644
645/**
646 * Selects the endpoint with the specified identifier from a union of endpoints.
647 *
648 * @stability unstable
649 * @category utility types
650 * @since 4.0.0
651 */
652export type WithIdentifier<Endpoints, Identifier extends string> = Extract<
653 Endpoints,
654 { readonly identifier: Identifier }
655>
656
657/**
658 * Removes endpoints with the specified identifier from a union of endpoints.
659 *
660 * @stability unstable
661 * @category utility types
662 * @since 4.0.0
663 */
664export type ExcludeIdentifier<Endpoints, Identifier extends string> = Exclude<
665 Endpoints,
666 { readonly identifier: Identifier }
667>
668
669/**
670 * Derives the normal handler type for the endpoint with the specified identifier
671 * in an endpoint union.
672 *
673 * @stability unstable
674 * @category utility types
675 * @since 4.0.0
676 */
677export type HandlerWithIdentifier<Endpoints extends Constraint, Identifier extends string, E, R> = Handler<
678 WithIdentifier<Endpoints, Identifier>,
679 E,
680 R
681>
682
683/**
684 * Derives the raw handler type for the endpoint with the specified identifier in
685 * an endpoint union.
686 *
687 * @stability unstable
688 * @category utility types
689 * @since 4.0.0
690 */
691export type HandlerRawWithIdentifier<Endpoints extends Constraint, Identifier extends string, E, R> = HandlerRaw<
692 WithIdentifier<Endpoints, Identifier>,
693 E,
694 R
695>
696
697/**
698 * Extracts the decoded success value type for the endpoint with the specified
699 * identifier in an endpoint union.
700 *
701 * @stability unstable
702 * @category utility types
703 * @since 4.0.0
704 */
705export type SuccessWithIdentifier<Endpoints extends Constraint, Identifier extends string> = Success<
706 WithIdentifier<Endpoints, Identifier>
707> extends infer S ? SuccessType<S> : never
708
709/**
710 * Computes the full error value union for the endpoint with the specified
711 * identifier in an endpoint union.
712 *
713 * @stability unstable
714 * @category utility types
715 * @since 4.0.0
716 */
717export type ErrorsWithIdentifier<Endpoints extends Constraint, Identifier extends string> = Errors<
718 WithIdentifier<Endpoints, Identifier>
719>
720
721/**
722 * Computes the server-side service requirements for the endpoint with the
723 * specified identifier in an endpoint union.
724 *
725 * @stability unstable
726 * @category utility types
727 * @since 4.0.0
728 */
729export type ServerServicesWithIdentifier<Endpoints extends Constraint, Identifier extends string> = ServerServices<
730 WithIdentifier<Endpoints, Identifier>
731>
732
733/**
734 * Extracts the middleware identifiers for the endpoint with the specified
735 * identifier in an endpoint union.
736 *
737 * @stability unstable
738 * @category utility types
739 * @since 4.0.0
740 */
741export type MiddlewareWithIdentifier<Endpoints extends Constraint, Identifier extends string> = Middleware<
742 WithIdentifier<Endpoints, Identifier>
743>
744
745/**
746 * Extracts the middleware service requirements for the endpoint with the
747 * specified identifier in an endpoint union.
748 *
749 * @stability unstable
750 * @category utility types
751 * @since 4.0.0
752 */
753export type MiddlewareServicesWithIdentifier<Endpoints extends Constraint, Identifier extends string> =
754 MiddlewareServices<WithIdentifier<Endpoints, Identifier>>
755
756/**
757 * Removes services provided by the HTTP router and the selected endpoint's
758 * middleware from a service requirement union.
759 *
760 * @stability unstable
761 * @category utility types
762 * @since 4.0.0
763 */
764export type ExcludeProvidedWithIdentifier<Endpoints extends Constraint, Identifier extends string, R> = ExcludeProvided<
765 WithIdentifier<Endpoints, Identifier>,
766 R
767>
768
769/**
770 * Removes services provided by the HTTP router and endpoint middleware from a
771 * service requirement union.
772 *
773 * @stability unstable
774 * @category utility types
775 * @since 4.0.0
776 */
777export type ExcludeProvided<Endpoint extends Constraint, R> = Exclude<
778 R,
779 | HttpRouter.Provided
780 | HttpApiMiddleware.Provides<Middleware<Endpoint>>
781>
782
783/**
784 * Returns an endpoint type with the supplied path prefix prepended while
785 * preserving the endpoint's schemas, method, errors, and middleware.
786 *
787 * @stability unstable
788 * @category utility types
789 * @since 4.0.0
790 */
791export type AddPrefix<Endpoint, Prefix extends HttpRouter.PathInput> = Endpoint extends HttpApiEndpoint<
792 infer _Identifier,
793 infer _Method,
794 infer _Path,
795 infer _Params,
796 infer _Query,
797 infer _Payload,
798 infer _Headers,
799 infer _Success,
800 infer _Error,
801 infer _M,
802 infer _MR
803> ? HttpApiEndpoint<
804 _Identifier,
805 _Method,
806 `${Prefix}${_Path}`,
807 _Params,
808 _Query,
809 _Payload,
810 _Headers,
811 _Success,
812 _Error,
813 _M,
814 _MR
815 > :
816 never
817
818/**
819 * Returns an endpoint type with additional middleware applied and the endpoint's
820 * middleware service requirements updated accordingly.
821 *
822 * @stability unstable
823 * @category utility types
824 * @since 4.0.0
825 */
826export type AddMiddleware<Endpoint, M extends HttpApiMiddleware.AnyId> = Endpoint extends HttpApiEndpoint<
827 infer _Identifier,
828 infer _Method,
829 infer _Path,
830 infer _Params,
831 infer _Query,
832 infer _Payload,
833 infer _Headers,
834 infer _Success,
835 infer _Error,
836 infer _M,
837 infer _MR
838> ? HttpApiEndpoint<
839 _Identifier,
840 _Method,
841 _Path,
842 _Params,
843 _Query,
844 _Payload,
845 _Headers,
846 _Success,
847 _Error,
848 _M | M,
849 HttpApiMiddleware.ApplyServices<M, _MR>
850 > :
851 never
852
853const Proto = {
854 [TypeId]: TypeId,
855 pipe() {
856 return pipeArguments(this, arguments)
857 },
858 prefix(this: Top, prefix: HttpRouter.PathInput) {
859 return makeProto({
860 ...optionsFromEndpoint(this),
861 path: HttpRouter.prefixPath(this.path, prefix)
862 })
863 },
864 middleware(this: Top, middleware: HttpApiMiddleware.AnyService) {
865 return makeProto({
866 ...optionsFromEndpoint(this),
867 middlewares: new Set([...this.middlewares, middleware as any])
868 })
869 },
870 annotate(this: Top, key: Context.Key<any, any>, value: any) {
871 return makeProto({
872 ...optionsFromEndpoint(this),
873 annotations: Context.add(this.annotations, key, value)
874 })
875 },
876 annotateMerge(this: Top, annotations: Context.Context<any>) {
877 return makeProto({
878 ...optionsFromEndpoint(this),
879 annotations: Context.merge(this.annotations, annotations)
880 })
881 }
882}
883
884const optionsFromEndpoint = (endpoint: Top) => ({
885 identifier: endpoint.identifier,
886 path: endpoint.path,
887 method: endpoint.method,
888 params: endpoint.params,
889 query: endpoint.query,
890 headers: endpoint.headers,
891 payload: endpoint.payload,
892 success: endpoint.success,
893 error: endpoint.error,
894 annotations: endpoint.annotations,
895 middlewares: endpoint.middlewares,
896 disableCodecs: endpoint.disableCodecs
897})
898
899function makeProto<
900 Identifier extends string,
901 Method extends HttpMethod,
902 const Path extends string,
903 Params extends Schema.Top,
904 Query extends Schema.Top,
905 Payload extends Schema.Top,
906 Headers extends Schema.Top,
907 Success extends Schema.Top,
908 Error extends Schema.Top,
909 Middleware,
910 MiddlewareR
911>(options: {
912 readonly identifier: Identifier
913 readonly path: Path
914 readonly method: Method
915 readonly params: Schema.Top | undefined
916 readonly query: Schema.Top | undefined
917 readonly headers: Schema.Top | undefined
918 readonly payload: PayloadMap
919 readonly success: ReadonlySet<Schema.Top>
920 readonly error: ReadonlySet<Schema.Top>
921 readonly annotations: Context.Context<never>
922 readonly middlewares: ReadonlySet<Context.Key<Middleware, any>>
923 readonly disableCodecs: boolean
924}): HttpApiEndpoint<
925 Identifier,
926 Method,
927 Path,
928 Params,
929 Query,
930 Payload,
931 Headers,
932 Success,
933 Error,
934 Middleware,
935 MiddlewareR
936> {
937 function HttpApiEndpoint() {}
938 Object.setPrototypeOf(HttpApiEndpoint, Proto)
939 return Object.assign(HttpApiEndpoint, options) as any
940}
941
942/**
943 * Constraint for path parameter schemas: each parameter must encode to
944 * `string | undefined`, or the schema must encode to a record of those values.
945 *
946 * @stability unstable
947 * @category utility types
948 * @since 4.0.0
949 */
950export type ParamsConstraint =
951 | Record<string, Schema.Encoder<string | undefined, unknown>>
952 | Schema.Encoder<Record<string, string | undefined>, unknown>
953
954/**
955 * Constraint for header schemas: each header must encode to `string | undefined`,
956 * or the schema must encode to a record of those values.
957 *
958 * @stability unstable
959 * @category utility types
960 * @since 4.0.0
961 */
962export type HeadersConstraint =
963 | Record<string, Schema.Encoder<string | undefined, unknown>>
964 | Schema.Encoder<Record<string, string | undefined>, unknown>
965
966/**
967 * Constraint for query schemas: each field must encode to `string`, an array of
968 * strings, or `undefined`, or the schema must encode to a record of those values.
969 *
970 * @stability unstable
971 * @category utility types
972 * @since 4.0.0
973 */
974export type QueryConstraint =
975 | Record<string, Schema.Encoder<string | ReadonlyArray<string> | undefined, unknown>>
976 | Schema.Encoder<string | ReadonlyArray<string> | undefined, unknown>
977
978/**
979 * Payload schema depends on the HTTP method:
980 * - for no-body methods, payload is modeled as query params, so each field must
981 * encode to `string | ReadonlyArray<string> | undefined` and OpenAPI can expand
982 * it into `in: query` parameters
983 * - for body methods, payload may be any `Schema.Top` (or content-type keyed
984 * schemas) and OpenAPI uses `requestBody` instead of `parameters`
985 *
986 * @stability unstable
987 * @category utility types
988 * @since 4.0.0
989 */
990export type PayloadConstraint<Method extends HttpMethod> = Method extends HttpMethod.NoBody ? Record<
991 string,
992 Schema.Encoder<string | ReadonlyArray<string> | undefined, unknown>
993 > :
994 Schema.Top | ReadonlyArray<Schema.Top>
995
996/**
997 * Payload constraint used when automatic codecs are enabled: no-body methods
998 * accept field records for query-style encoding, while body methods accept one or
999 * more schemas.
1000 *
1001 * @stability unstable
1002 * @category utility types
1003 * @since 4.0.0
1004 */
1005export type PayloadConstraintCodecs<Method extends HttpMethod> = Method extends HttpMethod.NoBody ?
1006 Record<string, Schema.Top> :
1007 Schema.Top | ReadonlyArray<Schema.Top>
1008
1009/**
1010 * Constraint for success response schemas, allowing either a single schema or a
1011 * readonly array of schemas.
1012 *
1013 * @stability unstable
1014 * @category utility types
1015 * @since 4.0.0
1016 */
1017export type SuccessConstraint = Schema.Top | ReadonlyArray<Schema.Top>
1018
1019/**
1020 * Constraint for error response schemas, allowing either a single schema or a
1021 * readonly array of schemas.
1022 *
1023 * @stability unstable
1024 * @category utility types
1025 * @since 4.0.0
1026 */
1027export type ErrorConstraint = Schema.Top | ReadonlyArray<Schema.Top>
1028
1029type ErrorSchema<S> = S extends HttpApiSchema.WithHeaders<infer Inner, Schema.Top> ? Inner : S
1030
1031type ErrorNoStream<S extends ErrorConstraint> = [
1032 Extract<
1033 ErrorSchema<S extends ReadonlyArray<Schema.Constraint> ? S[number] : S>,
1034 HttpApiSchema.StreamSchema
1035 >
1036] extends [never] ? S : never
1037
1038/**
1039 * Creates endpoint constructors for a specific HTTP method. The resulting
1040 * constructor builds an `HttpApiEndpoint` from an identifier, path, and optional request
1041 * and response schemas, applying automatic JSON or string-tree codecs unless
1042 * `disableCodecs` is enabled.
1043 *
1044 * @stability unstable
1045 * @category constructors
1046 * @since 4.0.0
1047 */
1048export const make = <Method extends HttpMethod>(method: Method): {
1049 <
1050 const Identifier extends string,
1051 const Path extends HttpRouter.PathInput,
1052 Params extends Schema.Top | Schema.Struct.Fields = never,
1053 Query extends Schema.Top | Schema.Struct.Fields = never,
1054 Payload extends PayloadConstraintCodecs<Method> = never,
1055 Headers extends Schema.Top | Schema.Struct.Fields = never,
1056 const Success extends SuccessConstraint = HttpApiSchema.NoContent,
1057 const Error extends Schema.Top | ReadonlyArray<Schema.Top> = never
1058 >(
1059 identifier: Identifier,
1060 path: Path,
1061 options?: {
1062 readonly disableCodecs?: false | undefined
1063 readonly params?: Params | undefined
1064 readonly query?: Query | undefined
1065 readonly headers?: Headers | undefined
1066 readonly payload?: Payload | undefined
1067 readonly success?: Success | undefined
1068 readonly error?: (Error & ErrorNoStream<Types.NoInfer<Error>>) | undefined
1069 }
1070 ): HttpApiEndpoint<
1071 Identifier,
1072 Method,
1073 Path,
1074 ToStringTreeCodec<Params>,
1075 ToStringTreeCodec<Query>,
1076 Method extends HttpMethod.WithBody ? ToJsonCodec<ToSchema<Payload>>
1077 : ToStringTreeCodec<ToSchema<Payload>>,
1078 ToStringTreeCodec<Headers>,
1079 ToSuccessCodec<Success>,
1080 ToJsonCodec<Error extends ReadonlyArray<Schema.Constraint> ? Error[number] : Error>
1081 >
1082 <
1083 const Identifier extends string,
1084 const Path extends HttpRouter.PathInput,
1085 Params extends ParamsConstraint = never,
1086 Query extends QueryConstraint = never,
1087 Payload extends PayloadConstraint<Method> = never,
1088 Headers extends HeadersConstraint = never,
1089 const Success extends SuccessConstraint = HttpApiSchema.NoContent,
1090 const Error extends ErrorConstraint = never
1091 >(
1092 identifier: Identifier,
1093 path: Path,
1094 options?: {
1095 readonly disableCodecs: true
1096 readonly params?: Params | undefined
1097 readonly query?: Query | undefined
1098 readonly headers?: Headers | undefined
1099 readonly payload?: Payload | undefined
1100 readonly success?: Success | undefined
1101 readonly error?: (Error & ErrorNoStream<Types.NoInfer<Error>>) | undefined
1102 }
1103 ): HttpApiEndpoint<
1104 Identifier,
1105 Method,
1106 Path,
1107 Params extends Schema.Struct.Fields ? Schema.Struct<Params> : Params,
1108 Query extends Schema.Struct.Fields ? Schema.Struct<Query> : Query,
1109 ToSchema<Payload>,
1110 ToSchema<Headers>,
1111 UnwrapReadonlyArray<Success>,
1112 Error extends ReadonlyArray<Schema.Constraint> ? Error[number] : Error
1113 >
1114} =>
1115<
1116 const Identifier extends string,
1117 const Path extends HttpRouter.PathInput,
1118 Params extends ParamsConstraint = never,
1119 Query extends QueryConstraint = never,
1120 Payload extends PayloadConstraint<Method> = never,
1121 Headers extends HeadersConstraint = never,
1122 const Success extends SuccessConstraint = HttpApiSchema.NoContent,
1123 const Error extends ErrorConstraint = never
1124>(
1125 identifier: Identifier,
1126 path: Path,
1127 options?: {
1128 readonly disableCodecs?: boolean | undefined
1129 readonly params?: Params | undefined
1130 readonly query?: Query | undefined
1131 readonly headers?: Headers | undefined
1132 readonly payload?: Payload | undefined
1133 readonly success?: Success | undefined
1134 readonly error?: (Error & ErrorNoStream<Types.NoInfer<Error>>) | undefined
1135 }
1136): HttpApiEndpoint<
1137 Identifier,
1138 Method,
1139 Path,
1140 Params extends Schema.Struct.Fields ? Schema.Struct<Params> : Params,
1141 Query extends Schema.Struct.Fields ? Schema.Struct<Query> : Query,
1142 Payload extends Schema.Struct.Fields ? Schema.Struct<Payload>
1143 : Payload extends ReadonlyArray<Schema.Constraint> ? Payload[number]
1144 : Payload,
1145 Headers extends Schema.Struct.Fields ? Schema.Struct<Headers> : Headers,
1146 UnwrapReadonlyArray<Success>,
1147 Error extends ReadonlyArray<Schema.Constraint> ? Error[number] : Error
1148> => {
1149 const disableCodecs = options?.disableCodecs ?? false
1150 const transformStringTree = disableCodecs ? identity : Schema.toCodecStringTree
1151 return makeProto({
1152 identifier,
1153 path,
1154 method,
1155 params: ensureStruct(options?.params, transformStringTree),
1156 query: ensureStruct(options?.query, transformStringTree),
1157 headers: ensureStruct(options?.headers, transformStringTree),
1158 payload: getPayload(options?.payload, method, disableCodecs),
1159 success: getSuccessResponse(options?.success, method, disableCodecs),
1160 error: getErrorResponse(options?.error, disableCodecs),
1161 annotations: Context.empty(),
1162 middlewares: new Set(),
1163 disableCodecs
1164 })
1165}
1166
1167type ToSchema<S extends Schema.Struct.Fields | Schema.Constraint | ReadonlyArray<Schema.Constraint>> = S extends
1168 Schema.Struct.Fields ? Schema.Struct<S>
1169 : S extends ReadonlyArray<Schema.Constraint> ? S[number]
1170 : S
1171
1172function ensureStruct(
1173 params: Schema.Struct.Fields | Schema.Top | undefined,
1174 transform: (schema: Schema.Top) => Schema.Top
1175): Schema.Top | undefined {
1176 if (params === undefined) return undefined
1177 if (Schema.isSchema(params)) return transform(params)
1178 return transform(Schema.Struct(params))
1179}
1180
1181function getPayload(
1182 payload: Schema.Constraint | ReadonlyArray<Schema.Constraint> | Schema.Struct.Fields | undefined,
1183 method: HttpMethod,
1184 disableCodecs: boolean
1185): PayloadMap {
1186 const result: Map<
1187 string,
1188 { encoding: HttpApiSchema.PayloadEncoding; schemas: [Schema.Top, ...Array<Schema.Top>] }
1189 > = new Map()
1190 if (payload === undefined) return result
1191 const schemas: Array<Schema.Top> = Array.isArray(payload)
1192 ? payload
1193 : Schema.isSchema(payload)
1194 ? [payload]
1195 : [(Schema.Struct(payload as any)).pipe(HttpApiSchema.asFormUrlEncoded())]
1196 const transform = disableCodecs ? identity : transformPayload
1197
1198 for (const schema of schemas) {
1199 const encoding = HttpApiSchema.getPayloadEncoding(schema.ast, method)
1200 const contentType = MediaType.normalize(encoding.contentType)
1201 const existing = result.get(contentType)
1202 if (existing) {
1203 if (existing.encoding._tag !== encoding._tag) {
1204 throw new Error(`Multiple payload encodings for content-type: ${encoding.contentType}`)
1205 }
1206 if (existing.encoding._tag === "Multipart") {
1207 throw new Error(`Multiple multipart payloads for content-type: ${encoding.contentType}`)
1208 }
1209 existing.schemas.push(transform(schema, method))
1210 } else {
1211 result.set(contentType, { encoding, schemas: [transform(schema, method)] })
1212 }
1213 }
1214 return result
1215}
1216
1217const reservedStreamFailureEvent = "effect/http-api/stream/failure"
1218
1219function getSuccessResponse(
1220 success: Schema.Top | ReadonlyArray<Schema.Top> | undefined,
1221 method: HttpMethod,
1222 disableCodecs: boolean
1223): Set<Schema.Top> {
1224 if (success === undefined) return new Set()
1225 const schemas = Arr.ensure(success)
1226 validateSuccessResponse(schemas, method)
1227 return new Set(disableCodecs ? schemas : schemas.map(transformResponseSchema))
1228}
1229
1230const transformResponseSchema = memoize((schema: Schema.Top): Schema.Top => {
1231 if (HttpApiSchema.isStreamSchema(schema)) return schema
1232 if (HttpApiSchema.isWithHeaders(schema)) {
1233 const inner = HttpApiSchema.isStreamSchema(schema.schema)
1234 ? schema.schema
1235 : applyResponseEncoding(schema.schema, HttpApiSchema.getResponseEncodingSchema(schema))
1236 return HttpApiSchema.rebuildWithHeaders(schema, inner, Schema.toCodecStringTree(schema.headers))
1237 }
1238 return transformResponse(schema)
1239})
1240
1241function getErrorResponse(
1242 error: Schema.Top | ReadonlyArray<Schema.Top> | undefined,
1243 disableCodecs: boolean
1244): Set<Schema.Top> {
1245 if (error === undefined) return new Set()
1246 const schemas = Arr.ensure(error)
1247 for (const schema of schemas) {
1248 const body = HttpApiSchema.isWithHeaders(schema) ? schema.schema : schema
1249 if (HttpApiSchema.isStreamSchema(body)) {
1250 throw new Error("Streaming schemas are not supported in error responses")
1251 }
1252 }
1253 validateResponseExclusivity(schemas, HttpApiSchema.getStatusErrorSchema)
1254 return new Set(disableCodecs ? schemas : schemas.map(transformResponseSchema))
1255}
1256
1257function validateSuccessResponse(schemas: ReadonlyArray<Schema.Constraint>, method: HttpMethod) {
1258 let hasStream = false
1259 const statuses = new Map<number, {
1260 readonly stream?: HttpApiSchema.StreamSchema | undefined
1261 bufferedContentTypes: Set<string>
1262 noContent: boolean
1263 }>()
1264
1265 for (const schema of schemas) {
1266 const inner = HttpApiSchema.isWithHeaders(schema) ? schema.schema : schema
1267 const status = HttpApiSchema.getStatusSuccessSchema(schema)
1268 if (HttpApiSchema.isStreamSchema(inner)) {
1269 validateStreamSuccess(inner, method)
1270 if (hasStream) {
1271 throw new Error("Multiple streaming success responses are not supported")
1272 }
1273 hasStream = true
1274 const entry = getStatusEntry(statuses, status)
1275 if (entry.noContent) {
1276 throw new Error(`Cannot combine no-content and streaming success responses for status: ${status}`)
1277 }
1278 if (entry.bufferedContentTypes.has(MediaType.normalize(inner.contentType))) {
1279 throw new Error(
1280 `Cannot combine buffered and streaming success responses for status ${status} and content-type: ${inner.contentType}`
1281 )
1282 }
1283 statuses.set(status, { ...entry, stream: inner })
1284 } else {
1285 const entry = getStatusEntry(statuses, status)
1286 const noContent = HttpApiSchema.isNoContent(inner.ast)
1287 if (entry.stream !== undefined) {
1288 if (noContent) {
1289 throw new Error(`Cannot combine no-content and streaming success responses for status: ${status}`)
1290 }
1291 const encoding = HttpApiSchema.getResponseEncodingSchema(schema)
1292 if (
1293 MediaType.normalize(encoding.contentType) === MediaType.normalize(entry.stream.contentType)
1294 ) {
1295 throw new Error(
1296 `Cannot combine buffered and streaming success responses for status ${status} and content-type: ${encoding.contentType}`
1297 )
1298 }
1299 }
1300 if (!noContent) {
1301 entry.bufferedContentTypes.add(
1302 MediaType.normalize(HttpApiSchema.getResponseEncodingSchema(schema).contentType)
1303 )
1304 }
1305 entry.noContent = entry.noContent || noContent
1306 }
1307 }
1308
1309 validateResponseExclusivity(schemas, HttpApiSchema.getStatusSuccessSchema)
1310}
1311
1312function getStatusEntry(
1313 statuses: Map<number, {
1314 readonly stream?: HttpApiSchema.StreamSchema | undefined
1315 bufferedContentTypes: Set<string>
1316 noContent: boolean
1317 }>,
1318 status: number
1319) {
1320 let entry = statuses.get(status)
1321 if (entry === undefined) {
1322 entry = { bufferedContentTypes: new Set(), noContent: false }
1323 statuses.set(status, entry)
1324 }
1325 return entry
1326}
1327
1328function validateResponseExclusivity(
1329 schemas: ReadonlyArray<Schema.Constraint>,
1330 getStatus: (schema: Schema.Constraint) => number
1331) {
1332 const statuses = new Map<number, {
1333 headerContentType: string | undefined
1334 readonly plainContentTypes: Set<string>
1335 }>()
1336 for (const schema of schemas) {
1337 const status = getStatus(schema)
1338 const withHeadersAnnotation = HttpApiSchema.getWithHeadersAnnotation(schema.ast)
1339 const body = HttpApiSchema.isWithHeaders(schema) ? schema.schema : withHeadersAnnotation?.body ?? schema
1340 const contentType = HttpApiSchema.isNoContent(body.ast)
1341 ? ""
1342 : MediaType.normalize(
1343 HttpApiSchema.isStreamSchema(body)
1344 ? body.contentType
1345 : HttpApiSchema.getResponseEncodingSchema(schema).contentType
1346 )
1347 let entry = statuses.get(status)
1348 if (entry === undefined) {
1349 entry = { headerContentType: undefined, plainContentTypes: new Set() }
1350 statuses.set(status, entry)
1351 }
1352 const combineError = () =>
1353 new Error(
1354 `Cannot combine a response with headers with another response for status ${status} and content-type: ${
1355 contentType || "<no content>"
1356 }`
1357 )
1358 if (HttpApiSchema.isWithHeaders(schema) || withHeadersAnnotation !== undefined) {
1359 if (entry.headerContentType !== undefined) {
1360 throw new Error(`Cannot declare multiple responses with headers for status ${status}`)
1361 }
1362 if (entry.plainContentTypes.has(contentType)) throw combineError()
1363 entry.headerContentType = contentType
1364 } else {
1365 if (entry.headerContentType === contentType) throw combineError()
1366 entry.plainContentTypes.add(contentType)
1367 }
1368 }
1369}
1370
1371function validateStreamSuccess(schema: HttpApiSchema.StreamSchema, method: HttpMethod) {
1372 if (method === "HEAD") {
1373 throw new Error("HEAD endpoints cannot declare streaming success responses")
1374 }
1375 if (HttpApiSchema.isStreamSse(schema) && hasReservedSseEventName(schema.events.ast)) {
1376 throw new Error(`SSE event name is reserved: ${reservedStreamFailureEvent}`)
1377 }
1378}
1379
1380function hasReservedSseEventName(ast: AST.AST): boolean {
1381 return hasReservedEventName(AST.toEncoded(ast), new Set())
1382}
1383
1384function hasReservedEventName(ast: AST.AST, seen: Set<AST.AST>): boolean {
1385 if (seen.has(ast)) return false
1386 seen.add(ast)
1387 if (AST.isUnion(ast)) {
1388 return ast.types.some((type) => hasReservedEventName(type, seen))
1389 }
1390 if (AST.isSuspend(ast)) {
1391 return hasReservedEventName(ast.thunk(), seen)
1392 }
1393 if (!AST.isObjects(ast)) return false
1394 const event = ast.propertySignatures.find((ps) => ps.name === "event")
1395 return event !== undefined && hasReservedEventLiteral(event.type, seen)
1396}
1397
1398function hasReservedEventLiteral(ast: AST.AST, seen: Set<AST.AST>): boolean {
1399 if (seen.has(ast)) return false
1400 seen.add(ast)
1401 const encoded = AST.toEncoded(ast)
1402 if (encoded !== ast) {
1403 return hasReservedEventLiteral(encoded, seen)
1404 }
1405 if (AST.isLiteral(ast)) {
1406 return ast.literal === reservedStreamFailureEvent
1407 }
1408 if (AST.isUnion(ast)) {
1409 return ast.types.some((type) => hasReservedEventLiteral(type, seen))
1410 }
1411 if (AST.isSuspend(ast)) {
1412 return hasReservedEventLiteral(ast.thunk(), seen)
1413 }
1414 return false
1415}
1416
1417function transformResponse(schema: Schema.Top): Schema.Top {
1418 const encoding = HttpApiSchema.getResponseEncoding(schema.ast)
1419 const withHeaders = HttpApiSchema.getWithHeadersAnnotation(schema.ast)
1420 if (withHeaders === undefined) {
1421 return applyResponseEncoding(schema, encoding)
1422 }
1423 return schema.annotate({
1424 "~httpApiWithHeaders": {
1425 ...withHeaders,
1426 bodyCodec: applyResponseEncoding(withHeaders.body, encoding),
1427 headersCodec: Schema.toCodecStringTree(withHeaders.headers)
1428 }
1429 })
1430}
1431
1432function applyResponseEncoding(schema: Schema.Top, encoding: HttpApiSchema.ResponseEncoding): Schema.Top {
1433 switch (encoding._tag) {
1434 case "Json":
1435 return Schema.toCodecJson(schema)
1436 case "FormUrlEncoded":
1437 return Schema.toCodecStringTree(schema)
1438 case "Text":
1439 case "Uint8Array":
1440 return schema
1441 }
1442}
1443
1444function transformPayload(schema: Schema.Top, method: HttpMethod): Schema.Top {
1445 const encoding = HttpApiSchema.getPayloadEncoding(schema.ast, method)
1446 switch (encoding._tag) {
1447 case "Json":
1448 return Schema.toCodecJson(schema)
1449 case "FormUrlEncoded":
1450 return Schema.toCodecStringTree(schema)
1451 case "Text":
1452 case "Uint8Array":
1453 case "Multipart":
1454 return schema
1455 }
1456}
1457
1458/**
1459 * Creates a `GET` endpoint declaration.
1460 *
1461 * @stability unstable
1462 * @category constructors
1463 * @since 4.0.0
1464 */
1465export const get = make("GET")
1466
1467/**
1468 * Creates a `QUERY` endpoint declaration.
1469 *
1470 * @stability unstable
1471 * @category constructors
1472 * @since 4.0.0
1473 */
1474export const query = make("QUERY")
1475
1476/**
1477 * Creates a `POST` endpoint declaration.
1478 *
1479 * @stability unstable
1480 * @category constructors
1481 * @since 4.0.0
1482 */
1483export const post = make("POST")
1484
1485/**
1486 * Creates a `PUT` endpoint declaration.
1487 *
1488 * @stability unstable
1489 * @category constructors
1490 * @since 4.0.0
1491 */
1492export const put = make("PUT")
1493
1494/**
1495 * Creates a `PATCH` endpoint declaration.
1496 *
1497 * @stability unstable
1498 * @category constructors
1499 * @since 4.0.0
1500 */
1501export const patch = make("PATCH")
1502
1503const del = make("DELETE")
1504
1505/**
1506 * @stability unstable
1507 */
1508export {
1509 /**
1510 * Creates a `DELETE` endpoint declaration.
1511 *
1512 * @stability unstable
1513 * @category constructors
1514 * @since 4.0.0
1515 */
1516 del as delete
1517}
1518
1519/**
1520 * Creates a `HEAD` endpoint declaration.
1521 *
1522 * @stability unstable
1523 * @category constructors
1524 * @since 4.0.0
1525 */
1526export const head = make("HEAD")
1527
1528/**
1529 * Creates an `OPTIONS` endpoint declaration.
1530 *
1531 * @stability unstable
1532 * @category constructors
1533 * @since 4.0.0
1534 */
1535export const options = make("OPTIONS")