/** * @since 1.0.0 */ import * as Context from "effect/Context" import { type Pipeable, pipeArguments } from "effect/Pipeable" import * as Predicate from "effect/Predicate" import * as Record from "effect/Record" import * as Schema from "effect/Schema" import type * as HttpApiEndpoint from "./HttpApiEndpoint.js" import type { HttpApiDecodeError } from "./HttpApiError.js" import type * as HttpApiMiddleware from "./HttpApiMiddleware.js" import * as HttpApiSchema from "./HttpApiSchema.js" /** * @since 1.0.0 * @category type ids */ export const TypeId: unique symbol = Symbol.for("@effect/platform/HttpApiGroup") /** * @since 1.0.0 * @category type ids */ export type TypeId = typeof TypeId /** * @since 1.0.0 * @category guards */ export const isHttpApiGroup = (u: unknown): u is HttpApiGroup.Any => Predicate.hasProperty(u, TypeId) /** * An `HttpApiGroup` is a collection of `HttpApiEndpoint`s. You can use an `HttpApiGroup` to * represent a portion of your domain. * * The endpoints can be implemented later using the `HttpApiBuilder.group` api. * * @since 1.0.0 * @category models */ export interface HttpApiGroup< out Id extends string, out Endpoints extends HttpApiEndpoint.HttpApiEndpoint.Any = never, in out Error = HttpApiDecodeError, out R = never, out TopLevel extends (true | false) = false > extends Pipeable { new(_: never): {} readonly [TypeId]: TypeId readonly identifier: Id readonly topLevel: TopLevel readonly endpoints: Record.ReadonlyRecord readonly errorSchema: Schema.Schema readonly annotations: Context.Context readonly middlewares: ReadonlySet /** * Add an `HttpApiEndpoint` to an `HttpApiGroup`. */ add( endpoint: A ): HttpApiGroup /** * Add an error schema to an `HttpApiGroup`, which is shared by all endpoints in the * group. */ addError( schema: Schema.Schema, annotations?: { readonly status?: number | undefined } ): HttpApiGroup /** * Add a path prefix to all endpoints in an `HttpApiGroup`. Note that this will only * add the prefix to the endpoints before this api is called. */ prefix(prefix: HttpApiEndpoint.PathSegment): HttpApiGroup /** * Add an `HttpApiMiddleware` to the `HttpApiGroup`. * * It will be applied to all endpoints in the group. */ middleware(middleware: Context.Tag): HttpApiGroup< Id, Endpoints, Error | HttpApiMiddleware.HttpApiMiddleware.Error, R | I | HttpApiMiddleware.HttpApiMiddleware.ErrorContext, TopLevel > /** * Add an `HttpApiMiddleware` to each endpoint in the `HttpApiGroup`. * * Endpoints added after this api is called will not have the middleware * applied. */ middlewareEndpoints( middleware: Context.Tag ): HttpApiGroup< Id, HttpApiEndpoint.HttpApiEndpoint.AddContext, Error, R, TopLevel > /** * Merge the annotations of an `HttpApiGroup` with a new context. */ annotateContext(context: Context.Context): HttpApiGroup /** * Add an annotation to an `HttpApiGroup`. */ annotate(tag: Context.Tag, value: S): HttpApiGroup /** * For each endpoint in an `HttpApiGroup`, update the annotations with a new * context. * * Note that this will only update the annotations before this api is called. */ annotateEndpointsContext(context: Context.Context): HttpApiGroup /** * For each endpoint in an `HttpApiGroup`, add an annotation. * * Note that this will only add the annotation to the endpoints before this api * is called. */ annotateEndpoints(tag: Context.Tag, value: S): HttpApiGroup } /** * @since 1.0.0 * @category models */ export interface ApiGroup { readonly _: unique symbol readonly apiId: ApiId readonly name: Name } /** * @since 1.0.0 * @category models */ export declare namespace HttpApiGroup { /** * @since 1.0.0 * @category models */ export interface Any { readonly [TypeId]: TypeId readonly identifier: string } /** * @since 1.0.0 * @category models */ export type AnyWithProps = HttpApiGroup /** * @since 1.0.0 * @category models */ export type ToService = A extends HttpApiGroup ? ApiGroup : never /** * @since 1.0.0 * @category models */ export type WithName = Extract /** * @since 1.0.0 * @category models */ export type Name = Group extends HttpApiGroup ? _Name : never /** * @since 1.0.0 * @category models */ export type Endpoints = Group extends HttpApiGroup ? _Endpoints : never /** * @since 1.0.0 * @category models */ export type EndpointsWithName = Endpoints> /** * @since 1.0.0 * @category models */ export type Error = Group extends HttpApiGroup ? _Error : never /** * @since 1.0.0 * @category models */ export type AddContext = [R] extends [never] ? Group : Group extends HttpApiGroup ? HttpApiGroup<_Name, _Endpoints, _Error, _R | R, _TopLevel> : never /** * @since 1.0.0 * @category models */ export type Provides = HttpApiMiddleware.HttpApiMiddleware.ExtractProvides> /** * @since 1.0.0 * @category models */ export type ErrorWithName = Error> /** * @since 1.0.0 * @category models */ export type Context = Group extends HttpApiGroup ? HttpApiMiddleware.HttpApiMiddleware.Without<_R> : never /** * @since 1.0.0 * @category models */ export type Middleware = Group extends HttpApiGroup ? HttpApiMiddleware.HttpApiMiddleware.Only<_R> : never /** * @since 1.0.0 * @category models */ export type ClientContext = Group extends HttpApiGroup ? | _R | HttpApiEndpoint.HttpApiEndpoint.Context<_Endpoints> | HttpApiEndpoint.HttpApiEndpoint.ErrorContext<_Endpoints> : never /** * @since 1.0.0 * @category models */ export type ErrorContext = Group extends HttpApiGroup ? HttpApiMiddleware.HttpApiMiddleware.Without<_R> | HttpApiEndpoint.HttpApiEndpoint.ErrorContext<_Endpoints> : never /** * @since 1.0.0 * @category models */ export type ContextWithName = Context> /** * @since 1.0.0 * @category models */ export type MiddlewareWithName = Middleware< WithName > } const Proto = { [TypeId]: TypeId, add(this: HttpApiGroup.AnyWithProps, endpoint: A) { return makeProto({ identifier: this.identifier, topLevel: this.topLevel, endpoints: { ...this.endpoints, [endpoint.name]: endpoint }, errorSchema: this.errorSchema, annotations: this.annotations, middlewares: this.middlewares }) }, addError( this: HttpApiGroup.AnyWithProps, schema: Schema.Schema, annotations?: { readonly status?: number } ) { return makeProto({ identifier: this.identifier, topLevel: this.topLevel, endpoints: this.endpoints, errorSchema: HttpApiSchema.UnionUnify( this.errorSchema, annotations?.status ? schema.annotations(HttpApiSchema.annotations({ status: annotations.status })) : schema ), annotations: this.annotations, middlewares: this.middlewares }) }, prefix(this: HttpApiGroup.AnyWithProps, prefix: HttpApiEndpoint.PathSegment) { return makeProto({ identifier: this.identifier, topLevel: this.topLevel, endpoints: Record.map(this.endpoints, (endpoint) => endpoint.prefix(prefix)), errorSchema: this.errorSchema, annotations: this.annotations, middlewares: this.middlewares }) }, middleware(this: HttpApiGroup.AnyWithProps, middleware: HttpApiMiddleware.TagClassAny) { return makeProto({ identifier: this.identifier, topLevel: this.topLevel, endpoints: this.endpoints, errorSchema: HttpApiSchema.UnionUnify(this.errorSchema, middleware.failure), annotations: this.annotations, middlewares: new Set([...this.middlewares, middleware]) }) }, middlewareEndpoints(this: HttpApiGroup.AnyWithProps, middleware: HttpApiMiddleware.TagClassAny) { return makeProto({ identifier: this.identifier, topLevel: this.topLevel, endpoints: Record.map(this.endpoints, (endpoint) => endpoint.middleware(middleware)), errorSchema: this.errorSchema, annotations: this.annotations, middlewares: this.middlewares }) }, annotateContext(this: HttpApiGroup.AnyWithProps, context: Context.Context) { return makeProto({ identifier: this.identifier, topLevel: this.topLevel, endpoints: this.endpoints, errorSchema: this.errorSchema, annotations: Context.merge(this.annotations, context), middlewares: this.middlewares }) }, annotate(this: HttpApiGroup.AnyWithProps, tag: Context.Tag, value: S) { return makeProto({ identifier: this.identifier, topLevel: this.topLevel, endpoints: this.endpoints, errorSchema: this.errorSchema, annotations: Context.add(this.annotations, tag, value), middlewares: this.middlewares }) }, annotateEndpointsContext(this: HttpApiGroup.AnyWithProps, context: Context.Context) { return makeProto({ identifier: this.identifier, topLevel: this.topLevel, endpoints: Record.map(this.endpoints, (endpoint) => endpoint.annotateContext(context)), errorSchema: this.errorSchema, annotations: this.annotations, middlewares: this.middlewares }) }, annotateEndpoints(this: HttpApiGroup.AnyWithProps, tag: Context.Tag, value: S) { return makeProto({ identifier: this.identifier, topLevel: this.topLevel, endpoints: Record.map(this.endpoints, (endpoint) => endpoint.annotate(tag, value)), errorSchema: this.errorSchema, annotations: this.annotations, middlewares: this.middlewares }) }, pipe() { return pipeArguments(this, arguments) } } const makeProto = < Id extends string, Endpoints extends HttpApiEndpoint.HttpApiEndpoint.Any, Error, R, TopLevel extends (true | false) >(options: { readonly identifier: Id readonly topLevel: TopLevel readonly endpoints: Record.ReadonlyRecord readonly errorSchema: Schema.Schema readonly annotations: Context.Context readonly middlewares: ReadonlySet }): HttpApiGroup => { function HttpApiGroup() {} Object.setPrototypeOf(HttpApiGroup, Proto) return Object.assign(HttpApiGroup, options) as any } /** * An `HttpApiGroup` is a collection of `HttpApiEndpoint`s. You can use an `HttpApiGroup` to * represent a portion of your domain. * * The endpoints can be implemented later using the `HttpApiBuilder.group` api. * * @since 1.0.0 * @category constructors */ export const make = (identifier: Id, options?: { readonly topLevel?: TopLevel | undefined }): HttpApiGroup => makeProto({ identifier, topLevel: options?.topLevel ?? false as any, endpoints: Record.empty(), errorSchema: Schema.Never as any, annotations: Context.empty(), middlewares: new Set() })