1/**
2 * Serves the proxy APIs generated from clustered entities.
3 *
4 * Proxy handlers read the target `entityId`, call the entity client, and
5 * forward the payload to the matching entity RPC method. This module provides
6 * handlers for HTTP API groups created by `EntityProxy.toHttpApiGroup` and RPC
7 * handler services for RPC groups created by `EntityProxy.toRpcGroup`. Both
8 * normal requests and discard requests are forwarded to the underlying entity
9 * client.
10 *
11 * @stability unstable
12 * @since 4.0.0
13 */
14import * as Context from "../Context.ts"
15import * as Effect from "../Effect.ts"
16import type * as HttpApi from "../http-api/HttpApi.ts"
17import * as HttpApiBuilder from "../http-api/HttpApiBuilder.ts"
18import type * as HttpApiGroup from "../http-api/HttpApiGroup.ts"
19import * as Layer from "../Layer.ts"
20import type * as Rpc from "../rpc/Rpc.ts"
21import type * as Entity from "./Entity.ts"
22import type { Sharding } from "./Sharding.ts"
23
24/**
25 * Creates HTTP API handlers for an entity proxy group.
26 *
27 * **Details**
28 *
29 * Each generated endpoint reads the `entityId` path parameter and forwards the
30 * request payload to the corresponding entity client method, including discard
31 * endpoints.
32 *
33 * @stability unstable
34 * @category layers
35 * @since 4.0.0
36 */
37export const layerHttpApi = <
38 ApiId extends string,
39 Groups extends HttpApiGroup.Constraint,
40 Identifier extends HttpApiGroup.Identifier<Groups>,
41 Type extends string,
42 Rpcs extends Rpc.Any
43>(
44 api: HttpApi.HttpApi<ApiId, Groups>,
45 identifier: Identifier,
46 entity: Entity.Entity<Type, Rpcs>
47): Layer.Layer<
48 HttpApiGroup.Service<ApiId, Identifier>,
49 never,
50 Sharding | Rpc.ServicesServer<Rpcs> | Rpc.ServicesClient<Rpcs>
51> =>
52 HttpApiBuilder.group(
53 api,
54 identifier,
55 Effect.fnUntraced(function*(handlers: any) {
56 const client = yield* entity.client
57 for (const parentRpc of entity.protocol.requests.values()) {
58 handlers = handlers
59 .handle(
60 parentRpc._tag,
61 ({ params, payload }: { params: { entityId: string }; payload: any }) =>
62 (client(params.entityId) as any as Record<string, (p: any) => Effect.Effect<any>>)[parentRpc._tag](
63 payload
64 ).pipe(
65 Effect.tapDefect(Effect.logError),
66 Effect.annotateLogs({
67 module: "EntityProxyServer",
68 entity: entity.type,
69 entityId: params.entityId,
70 method: parentRpc._tag
71 })
72 )
73 )
74 .handle(
75 `${parentRpc._tag}Discard`,
76 ({ params, payload }: { params: { entityId: string }; payload: any }) =>
77 (client(params.entityId) as any as Record<string, (p: any, o: {}) => Effect.Effect<any>>)[parentRpc._tag](
78 payload,
79 { discard: true }
80 ).pipe(
81 Effect.tapDefect(Effect.logError),
82 Effect.annotateLogs({
83 module: "EntityProxyServer",
84 entity: entity.type,
85 entityId: params.entityId,
86 method: `${parentRpc._tag}Discard`
87 })
88 )
89 )
90 }
91 return handlers as HttpApiBuilder.Handlers<never>
92 })
93 )
94
95/**
96 * Creates RPC handlers for the group produced by `EntityProxy.toRpcGroup`.
97 *
98 * **Details**
99 *
100 * The handlers forward each prefixed proxy RPC to the target entity client using
101 * the `entityId` embedded in the proxy payload.
102 *
103 * @stability unstable
104 * @category layers
105 * @since 4.0.0
106 */
107export const layerRpcHandlers = <
108 const Type extends string,
109 Rpcs extends Rpc.Any
110>(
111 entity: Entity.Entity<Type, Rpcs>
112): Layer.Layer<RpcHandlers<Rpcs, Type>, never, Sharding | Rpc.ServicesServer<Rpcs> | Rpc.ServicesClient<Rpcs>> =>
113 Layer.effectContext(Effect.gen(function*() {
114 const context = yield* Effect.context<never>()
115 const client = yield* entity.client
116 const handlers = new Map<string, Rpc.Handler<string>>()
117 for (const parentRpc_ of entity.protocol.requests.values()) {
118 const parentRpc = parentRpc_ as any as Rpc.AnyWithProps
119 const tag = `${entity.type}.${parentRpc._tag}` as const
120 const key = `effect/rpc/Rpc/${tag}`
121 handlers.set(key, {
122 context,
123 tag,
124 handler: ({ entityId, payload }: any) => (client(entityId) as any)[parentRpc._tag](payload) as any
125 } as any)
126 handlers.set(`${key}Discard`, {
127 context,
128 tag,
129 handler: ({ entityId, payload }: any) =>
130 (client(entityId) as any)[parentRpc._tag](payload, { discard: true }) as any
131 } as any)
132 }
133 return Context.makeUnsafe(handlers)
134 }))
135
136/**
137 * Union of RPC handler services required to serve the proxy RPCs for an entity.
138 *
139 * **Details**
140 *
141 * Includes both the normal prefixed RPC handler and its discard variant.
142 *
143 * @stability unstable
144 * @category services
145 * @since 4.0.0
146 */
147export type RpcHandlers<Rpcs extends Rpc.Any, Prefix extends string> = Rpcs extends Rpc.Rpc<
148 infer _Tag,
149 infer _Payload,
150 infer _Success,
151 infer _Error,
152 infer _Middleware,
153 infer _Requires
154> ? Rpc.Handler<`${Prefix}.${_Tag}`> | Rpc.Handler<`${Prefix}.${_Tag}Discard`>
155 : never