| 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 | */ |
| 14 | import * as Context from "../Context.ts" |
| 15 | import * as Effect from "../Effect.ts" |
| 16 | import type * as HttpApi from "../http-api/HttpApi.ts" |
| 17 | import * as HttpApiBuilder from "../http-api/HttpApiBuilder.ts" |
| 18 | import type * as HttpApiGroup from "../http-api/HttpApiGroup.ts" |
| 19 | import * as Layer from "../Layer.ts" |
| 20 | import type * as Rpc from "../rpc/Rpc.ts" |
| 21 | import type * as Entity from "./Entity.ts" |
| 22 | import 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 | */ |
| 37 | export 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 | */ |
| 107 | export 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 | */ |
| 147 | export 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 |