Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

array apis #626

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 35 additions & 24 deletions packages/effect-http-node/examples/example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,17 @@ const getLesnekEndpoint = Api.get("getLesnek", "/lesnek").pipe(
Api.setRequestQuery(Lesnek),
Api.setSecurity(Security.bearer({ name: "myAwesomeBearerAuth", bearerFormat: "JWT" }))
)
const getMilanEndpoint = Api.get("getMilan", "/milan").pipe(Api.setResponseBody(Schema.String))
const testEndpoint = Api.get("test", "/test").pipe(Api.setResponseBody(Standa), Api.setRequestQuery(Lesnek))
const standaEndpoint = Api.post("standa", "/standa").pipe(Api.setResponseBody(Standa), Api.setRequestBody(Standa))
const getMilanEndpoint = Api.get("getMilan", "/milan").pipe(
Api.setResponseBody(Schema.String)
)
const testEndpoint = Api.get("test", "/test").pipe(
Api.setResponseBody(Standa),
Api.setRequestQuery(Lesnek)
)
const standaEndpoint = Api.post("standa", "/standa").pipe(
Api.setResponseBody(Standa),
Api.setRequestBody(Standa)
)
const handleMilanEndpoint = Api.post("handleMilan", "/petr").pipe(
Api.setResponseBody(HumanSchema),
Api.setRequestBody(HumanSchema)
Expand All @@ -39,35 +47,38 @@ const callStandaEndpoint = Api.put("callStanda", "/api/zdar").pipe(
)

const api = Api.make({ title: "My awesome pets API", version: "1.0.0" }).pipe(
Api.addEndpoint(getLesnekEndpoint),
Api.addEndpoint(getMilanEndpoint),
Api.addEndpoint(testEndpoint),
Api.addEndpoint(standaEndpoint),
Api.addEndpoint(handleMilanEndpoint),
Api.addEndpoint(callStandaEndpoint)
Api.addEndpoints(
getLesnekEndpoint,
getMilanEndpoint,
testEndpoint,
standaEndpoint,
handleMilanEndpoint,
callStandaEndpoint
)
)

const getLesnekHandler = Handler.make(getLesnekEndpoint, ({ query }) =>
Effect.succeed(`hello ${query.name}`).pipe(
Effect.tap(() => Effect.logDebug("hello world"))
))
const handleMilanHandler = Handler.make(handleMilanEndpoint, ({ body }) =>
Effect.map(StuffService, ({ value }) => ({
...body,
randomValue: body.height + value
})))
const getLesnekHandler = Handler.make(
getLesnekEndpoint,
({ query }) => Effect.tap(Effect.succeed(`hello ${query.name}`), () => Effect.logDebug("hello world"))
)
const handleMilanHandler = Handler.make(
handleMilanEndpoint,
({ body }) => Effect.map(StuffService, ({ value }) => ({ ...body, randomValue: body.height + value }))
)
const getMilanHandler = Handler.make(getMilanEndpoint, () => Effect.succeed("Milan"))
const testHandler = Handler.make(testEndpoint, ({ query }) => Effect.succeed(query))
const standaHandler = Handler.make(standaEndpoint, ({ body }) => Effect.succeed(body))
const callStandaHandler = Handler.make(callStandaEndpoint, ({ body }) => Effect.succeed(body.zdar))

const app = RouterBuilder.make(api, { parseOptions: { errors: "all" } }).pipe(
RouterBuilder.handle(getLesnekHandler),
RouterBuilder.handle(handleMilanHandler),
RouterBuilder.handle(getMilanHandler),
RouterBuilder.handle(testHandler),
RouterBuilder.handle(standaHandler),
RouterBuilder.handle(callStandaHandler),
RouterBuilder.handle(
getLesnekHandler,
handleMilanHandler,
getMilanHandler,
testHandler,
standaHandler,
callStandaHandler
),
RouterBuilder.build
)

Expand Down
41 changes: 41 additions & 0 deletions packages/effect-http/dtslint/RouterBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { Schema } from "@effect/schema"
import { Effect } from "effect"
import { Api, Handler, RouterBuilder } from "effect-http"

declare const bodySchema: Schema.Schema<string, string, "R0">

const getArticleEndpoint = Api.get("getArticle", "/article").pipe(
Api.setResponseBody(bodySchema)
)
const getBookEndpoint = Api.get("getBook", "/book")

const api = Api.make().pipe(
Api.addEndpoint(getArticleEndpoint),
Api.addEndpoint(getBookEndpoint)
)

declare const getArticleHandler: Handler.Handler<typeof getArticleEndpoint, "E1", "R1" | "R2">
declare const getBookHandler: Handler.Handler<typeof getBookEndpoint, "E2" | "E3", "R3">

// $ExpectType RouterBuilder<never, "E1" | "E2" | "E3", "R0" | "R1" | "R2" | "R3">
RouterBuilder.make(api).pipe(
RouterBuilder.handle(getArticleHandler, getBookHandler)
)

const handler1 = Handler.make(getArticleEndpoint, () => Effect.succeed("article"))
const handler2 = Handler.make(getBookEndpoint, () => Effect.void)

// $ExpectType RouterBuilder<never, never, "R0">
RouterBuilder.make(api).pipe(
RouterBuilder.handle(handler1, handler2)
)

const api2 = Api.make().pipe(
Api.addEndpoint(getBookEndpoint)
)

// $ExpectType Default<never, SwaggerFiles>
RouterBuilder.make(api2).pipe(
RouterBuilder.handle(handler2),
RouterBuilder.build
)
8 changes: 8 additions & 0 deletions packages/effect-http/src/Api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,14 @@ export const addEndpoint: <E2 extends ApiEndpoint.ApiEndpoint.Any>(
endpoint: E2
) => <E1 extends ApiEndpoint.ApiEndpoint.Any>(api: Api<E1>) => Api<E1 | E2> = internal.addEndpoint

/**
* @category combining
* @since 1.0.0
*/
export const addEndpoints: <Endpoints extends ReadonlyArray<ApiEndpoint.ApiEndpoint.Any>>(
...endpoint: Endpoints
) => <E1 extends ApiEndpoint.ApiEndpoint.Any>(api: Api<E1>) => Api<E1 | Endpoints[number]> = internal.addEndpoints

/**
* @category combining
* @since 1.0.0
Expand Down
13 changes: 13 additions & 0 deletions packages/effect-http/src/ApiEndpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,19 @@ export declare namespace ApiEndpoint {
*/
export type Any = ApiEndpoint<AnyId, ApiRequest.ApiRequest.Any, ApiResponse.ApiResponse.Any, Security.Security.Any>

/**
* Any endpoint with `Request = Request.Any` and `Response = Response.Any`.
*
* @category models
* @since 1.0.0
*/
export type Unknown = ApiEndpoint<
AnyId,
ApiRequest.ApiRequest.Unknown,
ApiResponse.ApiResponse.Unknown,
Security.Security.Unknown
>

/**
* Default endpoint spec.
*
Expand Down
8 changes: 8 additions & 0 deletions packages/effect-http/src/ApiRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ export declare namespace ApiRequest {
*/
export type Any = ApiRequest<any, any, any, any, any>

/**
* Any request with all `Body`, `Path`, `Query` and `Headers` set to `any`.
*
* @category models
* @since 1.0.0
*/
export type Unknown = ApiRequest<unknown, unknown, unknown, unknown, unknown>

/**
* Default request.
*
Expand Down
8 changes: 8 additions & 0 deletions packages/effect-http/src/ApiResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ export declare namespace ApiResponse {
*/
export type Any = ApiResponse<AnyStatus, any, any, unknown>

/**
* Any request with all `Body`, `Path`, `Query` and `Headers` set to `Schema.Schema.Any`.
*
* @category models
* @since 1.0.0
*/
export type Unknown = ApiResponse<AnyStatus, unknown, unknown, unknown>

/**
* Default response.
*
Expand Down
34 changes: 33 additions & 1 deletion packages/effect-http/src/Handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export type TypeId = typeof TypeId
* @category models
* @since 1.0.0
*/
export interface Handler<A extends ApiEndpoint.ApiEndpoint.Any, E, R>
export interface Handler<A extends ApiEndpoint.ApiEndpoint.Any, E = never, R = never>
extends Handler.Variance<A, E, R>, Pipeable.Pipeable
{}

Expand Down Expand Up @@ -78,6 +78,30 @@ export declare namespace Handler {
*/
export type Any = Handler<ApiEndpoint.ApiEndpoint.Any, any, any>

/**
* @category models
* @since 1.0.0
*/
export type Unknown = Handler<ApiEndpoint.ApiEndpoint.Unknown, unknown, unknown>

/**
* @category models
* @since 1.0.0
*/
export type Endpoint<H extends Handler.Any> = [H] extends [Handler<infer A, any, any>] ? A : never

/**
* @category models
* @since 1.0.0
*/
export type Error<H extends Handler.Any> = [H] extends [Handler<any, infer E, any>] ? E : never

/**
* @category models
* @since 1.0.0
*/
export type Context<H extends Handler.Any> = [H] extends [Handler<any, any, infer C>] ? C : never

/**
* @category models
* @since 1.0.0
Expand Down Expand Up @@ -170,3 +194,11 @@ export const getRoute: <A extends ApiEndpoint.ApiEndpoint.Any, E, R>(
export const getEndpoint: <A extends ApiEndpoint.ApiEndpoint.Any, E, R>(
handler: Handler<A, E, R>
) => A = internal.getEndpoint

/**
* @category refinements
* @since 1.0.0
*/
export const isHandler: (
u: unknown
) => u is Handler<ApiEndpoint.ApiEndpoint.Unknown, unknown, unknown> = internal.isHandler
15 changes: 8 additions & 7 deletions packages/effect-http/src/RouterBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,14 @@ export const handleRaw: <
* @since 1.0.0
*/
export const handle: {
<R2, E2, A extends ApiEndpoint.ApiEndpoint.Any, Endpoint extends A>(
handler: Handler.Handler<Endpoint, E2, R2>
): <R1, E1>(builder: RouterBuilder<A, E1, R1>) => RouterBuilder<
Exclude<A, Endpoint>,
E1 | Exclude<E2, HttpError.HttpError>,
| Exclude<R1 | R2, HttpRouter.RouteContext | HttpServerRequest.HttpServerRequest>
| ApiEndpoint.ApiEndpoint.Context<Endpoint>
<H extends ReadonlyArray<Handler.Handler.Any>>(
...handler: H
): <A extends ApiEndpoint.ApiEndpoint.Any, R1, E1>(builder: RouterBuilder<A, E1, R1>) => RouterBuilder<
Exclude<A, Handler.Handler.Endpoint<H[number]>>,
E1 | Exclude<Handler.Handler.Error<H[number]>, HttpError.HttpError>,
| Exclude<R1 | Handler.Handler.Context<H[number]>, HttpRouter.RouteContext | HttpServerRequest.HttpServerRequest>
| Handler.Handler.Context<H[number]>
| ApiEndpoint.ApiEndpoint.Context<Handler.Handler.Endpoint<H[number]>>
>

<A extends ApiEndpoint.ApiEndpoint.Any, E2, R2, Id extends ApiEndpoint.ApiEndpoint.Id<A>>(
Expand Down
6 changes: 6 additions & 0 deletions packages/effect-http/src/Security.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ export declare namespace Security {
*/
export type Any = Security<any, any, any>

/**
* @category models
* @since 1.0.0
*/
export type Unknown = Security<unknown, unknown, unknown>

/**
* @category models
* @since 1.0.0
Expand Down
7 changes: 7 additions & 0 deletions packages/effect-http/src/internal/api.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Iterable } from "effect"
import * as Array from "effect/Array"
import * as Pipeable from "effect/Pipeable"
import type * as Api from "../Api.js"
Expand Down Expand Up @@ -62,6 +63,12 @@ export const addEndpoint =
return new ApiImpl([...groupsWithoutDefault, newDefaultGroup], api.options) as any
}

/** @internal */
export const addEndpoints =
<A2 extends ReadonlyArray<ApiEndpoint.ApiEndpoint.Any>>(...endpoints: A2) =>
<A1 extends ApiEndpoint.ApiEndpoint.Any>(api: Api.Api<A1>): Api.Api<A1 | A2[number]> =>
Iterable.reduce(endpoints, api as Api.Api<A1 | A2[number]>, (api, endpoint) => addEndpoint(endpoint)(api))

/** @internal */
export const addGroup = <E2 extends ApiEndpoint.ApiEndpoint.Any>(
group: ApiGroup.ApiGroup<E2>
Expand Down
4 changes: 4 additions & 0 deletions packages/effect-http/src/internal/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,7 @@ export const getRoute = <A extends ApiEndpoint.ApiEndpoint.Any, E, R>(
export const getEndpoint = <A extends ApiEndpoint.ApiEndpoint.Any, E, R>(
handler: Handler.Handler<A, E, R>
): A => (handler as HandlerImpl<A, E, R>).endpoint

/** @internal */
export const isHandler = (u: unknown): u is Handler.Handler.Unknown =>
typeof u === "object" && u !== null && TypeId in u
41 changes: 29 additions & 12 deletions packages/effect-http/src/internal/router-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as HttpRouter from "@effect/platform/HttpRouter"
import type * as HttpServerRequest from "@effect/platform/HttpServerRequest"
import * as Effect from "effect/Effect"
import { dual } from "effect/Function"
import * as Iterable from "effect/Iterable"
import * as Pipeable from "effect/Pipeable"
import type * as Scope from "effect/Scope"

Expand Down Expand Up @@ -113,22 +114,38 @@ const getRemainingEndpoint = <

/** @internal */
export const handle = (...args: Array<any>) => (builder: RouterBuilder.RouterBuilder.Any): any => {
if (args.length === 2) {
const [id, handler] = args
const endpoint = getRemainingEndpoint(builder, id)
return handle(Handler.make(endpoint, handler))(builder)
if (Handler.isHandler(args[0])) {
return (args as ReadonlyArray<Handler.Handler.Any>).reduce((builder, handler) => {
const remainingEndpoints = removeRemainingEndpoint(
builder,
ApiEndpoint.getId(Handler.getEndpoint(handler))
)
const router = addRoute(builder.router, Handler.getRoute(handler))

return new RouterBuilderImpl(remainingEndpoints, builder.api, router, builder.options)
}, builder)
}

const handler = args[0] as Handler.Handler.Any
const remainingEndpoints = removeRemainingEndpoint(
builder,
ApiEndpoint.getId(Handler.getEndpoint(handler))
)
const router = addRoute(builder.router, Handler.getRoute(handler))

return new RouterBuilderImpl(remainingEndpoints, builder.api, router, builder.options)
const [id, handler] = args
const endpoint = getRemainingEndpoint(builder, id)
return handle(Handler.make(endpoint, handler))(builder)
}

/** @internal */
export const handleMany =
<A extends ApiEndpoint.ApiEndpoint.Any, H extends Handler.Handler.Any>(handlers: Iterable<H>) =>
<R1, E1>(builder: RouterBuilder.RouterBuilder<A, E1, R1>) =>
Iterable.reduce(
handlers,
builder as RouterBuilder.RouterBuilder<
Exclude<A, Handler.Handler.Endpoint<H>>,
E1 | Exclude<Handler.Handler.Error<H>, HttpError.HttpError>,
| Exclude<R1 | Handler.Handler.Context<H>, HttpRouter.RouteContext | HttpServerRequest.HttpServerRequest>
| ApiEndpoint.ApiEndpoint.Context<Handler.Handler.Context<H>>
>,
(builder, handler) => handle(handler)(builder)
)

/** @internal */
export const handler = <A extends Api.Api.Any, E, R, Id extends Api.Api.Ids<A>>(
api: A,
Expand Down