Skip to content

Commit eb15c5c

Browse files
committed
array apis
1 parent 16c76f7 commit eb15c5c

File tree

12 files changed

+174
-43
lines changed

12 files changed

+174
-43
lines changed

packages/effect-http-node/examples/example.ts

+35-24
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,17 @@ const getLesnekEndpoint = Api.get("getLesnek", "/lesnek").pipe(
2626
Api.setRequestQuery(Lesnek),
2727
Api.setSecurity(Security.bearer({ name: "myAwesomeBearerAuth", bearerFormat: "JWT" }))
2828
)
29-
const getMilanEndpoint = Api.get("getMilan", "/milan").pipe(Api.setResponseBody(Schema.String))
30-
const testEndpoint = Api.get("test", "/test").pipe(Api.setResponseBody(Standa), Api.setRequestQuery(Lesnek))
31-
const standaEndpoint = Api.post("standa", "/standa").pipe(Api.setResponseBody(Standa), Api.setRequestBody(Standa))
29+
const getMilanEndpoint = Api.get("getMilan", "/milan").pipe(
30+
Api.setResponseBody(Schema.String)
31+
)
32+
const testEndpoint = Api.get("test", "/test").pipe(
33+
Api.setResponseBody(Standa),
34+
Api.setRequestQuery(Lesnek)
35+
)
36+
const standaEndpoint = Api.post("standa", "/standa").pipe(
37+
Api.setResponseBody(Standa),
38+
Api.setRequestBody(Standa)
39+
)
3240
const handleMilanEndpoint = Api.post("handleMilan", "/petr").pipe(
3341
Api.setResponseBody(HumanSchema),
3442
Api.setRequestBody(HumanSchema)
@@ -39,35 +47,38 @@ const callStandaEndpoint = Api.put("callStanda", "/api/zdar").pipe(
3947
)
4048

4149
const api = Api.make({ title: "My awesome pets API", version: "1.0.0" }).pipe(
42-
Api.addEndpoint(getLesnekEndpoint),
43-
Api.addEndpoint(getMilanEndpoint),
44-
Api.addEndpoint(testEndpoint),
45-
Api.addEndpoint(standaEndpoint),
46-
Api.addEndpoint(handleMilanEndpoint),
47-
Api.addEndpoint(callStandaEndpoint)
50+
Api.addEndpoints(
51+
getLesnekEndpoint,
52+
getMilanEndpoint,
53+
testEndpoint,
54+
standaEndpoint,
55+
handleMilanEndpoint,
56+
callStandaEndpoint
57+
)
4858
)
4959

50-
const getLesnekHandler = Handler.make(getLesnekEndpoint, ({ query }) =>
51-
Effect.succeed(`hello ${query.name}`).pipe(
52-
Effect.tap(() => Effect.logDebug("hello world"))
53-
))
54-
const handleMilanHandler = Handler.make(handleMilanEndpoint, ({ body }) =>
55-
Effect.map(StuffService, ({ value }) => ({
56-
...body,
57-
randomValue: body.height + value
58-
})))
60+
const getLesnekHandler = Handler.make(
61+
getLesnekEndpoint,
62+
({ query }) => Effect.tap(Effect.succeed(`hello ${query.name}`), () => Effect.logDebug("hello world"))
63+
)
64+
const handleMilanHandler = Handler.make(
65+
handleMilanEndpoint,
66+
({ body }) => Effect.map(StuffService, ({ value }) => ({ ...body, randomValue: body.height + value }))
67+
)
5968
const getMilanHandler = Handler.make(getMilanEndpoint, () => Effect.succeed("Milan"))
6069
const testHandler = Handler.make(testEndpoint, ({ query }) => Effect.succeed(query))
6170
const standaHandler = Handler.make(standaEndpoint, ({ body }) => Effect.succeed(body))
6271
const callStandaHandler = Handler.make(callStandaEndpoint, ({ body }) => Effect.succeed(body.zdar))
6372

6473
const app = RouterBuilder.make(api, { parseOptions: { errors: "all" } }).pipe(
65-
RouterBuilder.handle(getLesnekHandler),
66-
RouterBuilder.handle(handleMilanHandler),
67-
RouterBuilder.handle(getMilanHandler),
68-
RouterBuilder.handle(testHandler),
69-
RouterBuilder.handle(standaHandler),
70-
RouterBuilder.handle(callStandaHandler),
74+
RouterBuilder.handle(
75+
getLesnekHandler,
76+
handleMilanHandler,
77+
getMilanHandler,
78+
testHandler,
79+
standaHandler,
80+
callStandaHandler
81+
),
7182
RouterBuilder.build
7283
)
7384

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Api, type Handler, RouterBuilder } from "effect-http"
2+
3+
const getArticleEndpoint = Api.get("getArticle", "/article")
4+
const getBookEndpoint = Api.get("getBook", "/book")
5+
6+
const api = Api.make().pipe(
7+
Api.addEndpoint(getArticleEndpoint),
8+
Api.addEndpoint(getBookEndpoint)
9+
)
10+
11+
declare const getArticleHandler: Handler.Handler<typeof getArticleEndpoint, "E1", "R1" | "R2">
12+
declare const getBookHandler: Handler.Handler<typeof getBookEndpoint, "E2" | "E3", "R3">
13+
14+
// $ExpectType RouterBuilder<never, "E1" | "E2" | "E3", "R1" | "R2" | "R3">
15+
RouterBuilder.make(api).pipe(
16+
RouterBuilder.handle(getArticleHandler, getBookHandler)
17+
)

packages/effect-http/src/Api.ts

+8
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,14 @@ export const addEndpoint: <E2 extends ApiEndpoint.ApiEndpoint.Any>(
124124
endpoint: E2
125125
) => <E1 extends ApiEndpoint.ApiEndpoint.Any>(api: Api<E1>) => Api<E1 | E2> = internal.addEndpoint
126126

127+
/**
128+
* @category combining
129+
* @since 1.0.0
130+
*/
131+
export const addEndpoints: <Endpoints extends ReadonlyArray<ApiEndpoint.ApiEndpoint.Any>>(
132+
...endpoint: Endpoints
133+
) => <E1 extends ApiEndpoint.ApiEndpoint.Any>(api: Api<E1>) => Api<E1 | Endpoints[number]> = internal.addEndpoints
134+
127135
/**
128136
* @category combining
129137
* @since 1.0.0

packages/effect-http/src/ApiEndpoint.ts

+13
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,19 @@ export declare namespace ApiEndpoint {
8989
*/
9090
export type Any = ApiEndpoint<AnyId, ApiRequest.ApiRequest.Any, ApiResponse.ApiResponse.Any, Security.Security.Any>
9191

92+
/**
93+
* Any endpoint with `Request = Request.Any` and `Response = Response.Any`.
94+
*
95+
* @category models
96+
* @since 1.0.0
97+
*/
98+
export type Unknown = ApiEndpoint<
99+
AnyId,
100+
ApiRequest.ApiRequest.Unknown,
101+
ApiResponse.ApiResponse.Unknown,
102+
Security.Security.Unknown
103+
>
104+
92105
/**
93106
* Default endpoint spec.
94107
*

packages/effect-http/src/ApiRequest.ts

+8
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ export declare namespace ApiRequest {
5353
*/
5454
export type Any = ApiRequest<any, any, any, any, any>
5555

56+
/**
57+
* Any request with all `Body`, `Path`, `Query` and `Headers` set to `any`.
58+
*
59+
* @category models
60+
* @since 1.0.0
61+
*/
62+
export type Unknown = ApiRequest<unknown, unknown, unknown, unknown, unknown>
63+
5664
/**
5765
* Default request.
5866
*

packages/effect-http/src/ApiResponse.ts

+8
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ export declare namespace ApiResponse {
6363
*/
6464
export type Any = ApiResponse<AnyStatus, any, any, unknown>
6565

66+
/**
67+
* Any request with all `Body`, `Path`, `Query` and `Headers` set to `Schema.Schema.Any`.
68+
*
69+
* @category models
70+
* @since 1.0.0
71+
*/
72+
export type Unknown = ApiResponse<AnyStatus, unknown, unknown, unknown>
73+
6674
/**
6775
* Default response.
6876
*

packages/effect-http/src/Handler.ts

+32
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,30 @@ export declare namespace Handler {
7878
*/
7979
export type Any = Handler<ApiEndpoint.ApiEndpoint.Any, any, any>
8080

81+
/**
82+
* @category models
83+
* @since 1.0.0
84+
*/
85+
export type Unknown = Handler<ApiEndpoint.ApiEndpoint.Unknown, unknown, unknown>
86+
87+
/**
88+
* @category models
89+
* @since 1.0.0
90+
*/
91+
export type Endpoint<H extends Handler.Any> = [H] extends [Handler<infer A, any, any>] ? A : never
92+
93+
/**
94+
* @category models
95+
* @since 1.0.0
96+
*/
97+
export type Error<H extends Handler.Any> = [H] extends [Handler<any, infer E, any>] ? E : never
98+
99+
/**
100+
* @category models
101+
* @since 1.0.0
102+
*/
103+
export type Context<H extends Handler.Any> = [H] extends [Handler<any, any, infer C>] ? C : never
104+
81105
/**
82106
* @category models
83107
* @since 1.0.0
@@ -170,3 +194,11 @@ export const getRoute: <A extends ApiEndpoint.ApiEndpoint.Any, E, R>(
170194
export const getEndpoint: <A extends ApiEndpoint.ApiEndpoint.Any, E, R>(
171195
handler: Handler<A, E, R>
172196
) => A = internal.getEndpoint
197+
198+
/**
199+
* @category refinements
200+
* @since 1.0.0
201+
*/
202+
export const isHandler: (
203+
u: unknown
204+
) => u is Handler<ApiEndpoint.ApiEndpoint.Unknown, unknown, unknown> = internal.isHandler

packages/effect-http/src/RouterBuilder.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -133,13 +133,13 @@ export const handleRaw: <
133133
* @since 1.0.0
134134
*/
135135
export const handle: {
136-
<R2, E2, A extends ApiEndpoint.ApiEndpoint.Any, Endpoint extends A>(
137-
handler: Handler.Handler<Endpoint, E2, R2>
138-
): <R1, E1>(builder: RouterBuilder<A, E1, R1>) => RouterBuilder<
139-
Exclude<A, Endpoint>,
140-
E1 | Exclude<E2, HttpError.HttpError>,
141-
| Exclude<R1 | R2, HttpRouter.RouteContext | HttpServerRequest.HttpServerRequest>
142-
| ApiEndpoint.ApiEndpoint.Context<Endpoint>
136+
<H extends ReadonlyArray<Handler.Handler.Any>>(
137+
...handler: H
138+
): <A extends ApiEndpoint.ApiEndpoint.Any, R1, E1>(builder: RouterBuilder<A, E1, R1>) => RouterBuilder<
139+
Exclude<A, Handler.Handler.Endpoint<H[number]>>,
140+
E1 | Exclude<Handler.Handler.Error<H[number]>, HttpError.HttpError>,
141+
| Exclude<R1 | Handler.Handler.Context<H[number]>, HttpRouter.RouteContext | HttpServerRequest.HttpServerRequest>
142+
| ApiEndpoint.ApiEndpoint.Context<Handler.Handler.Context<H[number]>>
143143
>
144144

145145
<A extends ApiEndpoint.ApiEndpoint.Any, E2, R2, Id extends ApiEndpoint.ApiEndpoint.Id<A>>(

packages/effect-http/src/Security.ts

+6
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ export declare namespace Security {
5454
*/
5555
export type Any = Security<any, any, any>
5656

57+
/**
58+
* @category models
59+
* @since 1.0.0
60+
*/
61+
export type Unknown = Security<unknown, unknown, unknown>
62+
5763
/**
5864
* @category models
5965
* @since 1.0.0

packages/effect-http/src/internal/api.ts

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Iterable } from "effect"
12
import * as Array from "effect/Array"
23
import * as Pipeable from "effect/Pipeable"
34
import type * as Api from "../Api.js"
@@ -62,6 +63,12 @@ export const addEndpoint =
6263
return new ApiImpl([...groupsWithoutDefault, newDefaultGroup], api.options) as any
6364
}
6465

66+
/** @internal */
67+
export const addEndpoints =
68+
<A2 extends ReadonlyArray<ApiEndpoint.ApiEndpoint.Any>>(...endpoints: A2) =>
69+
<A1 extends ApiEndpoint.ApiEndpoint.Any>(api: Api.Api<A1>): Api.Api<A1 | A2[number]> =>
70+
Iterable.reduce(endpoints, api as Api.Api<A1 | A2[number]>, (api, endpoint) => addEndpoint(endpoint)(api))
71+
6572
/** @internal */
6673
export const addGroup = <E2 extends ApiEndpoint.ApiEndpoint.Any>(
6774
group: ApiGroup.ApiGroup<E2>

packages/effect-http/src/internal/handler.ts

+4
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,7 @@ export const getRoute = <A extends ApiEndpoint.ApiEndpoint.Any, E, R>(
118118
export const getEndpoint = <A extends ApiEndpoint.ApiEndpoint.Any, E, R>(
119119
handler: Handler.Handler<A, E, R>
120120
): A => (handler as HandlerImpl<A, E, R>).endpoint
121+
122+
/** @internal */
123+
export const isHandler = (u: unknown): u is Handler.Handler.Unknown =>
124+
typeof u === "object" && u !== null && TypeId in u

packages/effect-http/src/internal/router-builder.ts

+29-12
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as HttpRouter from "@effect/platform/HttpRouter"
33
import type * as HttpServerRequest from "@effect/platform/HttpServerRequest"
44
import * as Effect from "effect/Effect"
55
import { dual } from "effect/Function"
6+
import * as Iterable from "effect/Iterable"
67
import * as Pipeable from "effect/Pipeable"
78
import type * as Scope from "effect/Scope"
89

@@ -113,22 +114,38 @@ const getRemainingEndpoint = <
113114

114115
/** @internal */
115116
export const handle = (...args: Array<any>) => (builder: RouterBuilder.RouterBuilder.Any): any => {
116-
if (args.length === 2) {
117-
const [id, handler] = args
118-
const endpoint = getRemainingEndpoint(builder, id)
119-
return handle(Handler.make(endpoint, handler))(builder)
117+
if (Handler.isHandler(args[0])) {
118+
return (args as ReadonlyArray<Handler.Handler.Any>).reduce((builder, handler) => {
119+
const remainingEndpoints = removeRemainingEndpoint(
120+
builder,
121+
ApiEndpoint.getId(Handler.getEndpoint(handler))
122+
)
123+
const router = addRoute(builder.router, Handler.getRoute(handler))
124+
125+
return new RouterBuilderImpl(remainingEndpoints, builder.api, router, builder.options)
126+
}, builder)
120127
}
121128

122-
const handler = args[0] as Handler.Handler.Any
123-
const remainingEndpoints = removeRemainingEndpoint(
124-
builder,
125-
ApiEndpoint.getId(Handler.getEndpoint(handler))
126-
)
127-
const router = addRoute(builder.router, Handler.getRoute(handler))
128-
129-
return new RouterBuilderImpl(remainingEndpoints, builder.api, router, builder.options)
129+
const [id, handler] = args
130+
const endpoint = getRemainingEndpoint(builder, id)
131+
return handle(Handler.make(endpoint, handler))(builder)
130132
}
131133

134+
/** @internal */
135+
export const handleMany =
136+
<A extends ApiEndpoint.ApiEndpoint.Any, H extends Handler.Handler.Any>(handlers: Iterable<H>) =>
137+
<R1, E1>(builder: RouterBuilder.RouterBuilder<A, E1, R1>) =>
138+
Iterable.reduce(
139+
handlers,
140+
builder as RouterBuilder.RouterBuilder<
141+
Exclude<A, Handler.Handler.Endpoint<H>>,
142+
E1 | Exclude<Handler.Handler.Error<H>, HttpError.HttpError>,
143+
| Exclude<R1 | Handler.Handler.Context<H>, HttpRouter.RouteContext | HttpServerRequest.HttpServerRequest>
144+
| ApiEndpoint.ApiEndpoint.Context<Handler.Handler.Context<H>>
145+
>,
146+
(builder, handler) => handle(handler)(builder)
147+
)
148+
132149
/** @internal */
133150
export const handler = <A extends Api.Api.Any, E, R, Id extends Api.Api.Ids<A>>(
134151
api: A,

0 commit comments

Comments
 (0)